设计模式之(六)原型模式(ProtoType)
认识原型模式
原型模式是比较简单的设计模式。废话不多说,直接看定义:用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。通过实例指定种类,种类就是初始化的类,然后通过拷贝创建对象。先展示一个实现的原型模式的例子
1 public class Product { 2 3 private String proID; 4 5 private String proName; 6 7 private String proDescption; 8 9 public String getProID() { 10 return proID; 11 } 12 13 public void setProID(String proID) { 14 this.proID = proID; 15 } 16 17 public String getProName() { 18 return proName; 19 } 20 21 public void setProName(String proName) { 22 this.proName = proName; 23 } 24 25 public String getProDescption() { 26 return proDescption; 27 } 28 29 public void setProDescption(String proDescption) { 30 this.proDescption = proDescption; 31 } 32 33 public void showPro(){ 34 System.out.println("ID:"+this.getProID()+";Name:"+this.getProName()+";Descrip:"+this.getProDescption()); 35 System.out.println("-------------------------------------"); 36 } 37 38 //关键方法 39 public Product cloneself(){ 40 Product p = new Product(); 41 p.setProName(this.proID); 42 p.setProID(this.proID); 43 p.setProDescption(this.proDescption); 44 45 return p; 46 } 47 } 48 49 public class Client { 50 public static void main(String[] args) { 51 Product pp = new Product(); 52 pp.setProID("P0001"); 53 pp.setProName("玩具一号"); 54 pp.setProDescption("此为玩具一号的初代产品"); 55 56 pp.showPro(); 57 58 //拷贝 59 Product pp2 = pp.cloneself(); 60 pp2.showPro(); 61 pp2.setProDescption("此为玩具一号的升级产品"); 62 pp2.showPro(); 63 64 //原来的 65 pp.showPro(); 66 } 67 } 68 69 //实行效果 70 ID:P0001;Name:玩具一号;Descrip:此为玩具一号的初代产品 71 ------------------------------------- 72 ID:P0001;Name:P0001;Descrip:此为玩具一号的初代产品 73 ------------------------------------- 74 ID:P0001;Name:P0001;Descrip:此为玩具一号的升级产品 75 ------------------------------------- 76 ID:P0001;Name:玩具一号;Descrip:此为玩具一号的初代产品 77 -------------------------------------
通过例子可以看出来,原型模式的核心就是克隆自己的方法,在例子中就是 cloneself,实例对象调用的 cloneself ,就识别出来了对象是哪个类的。同时拷贝了一个新的对象,和原来的实例是完全分离的两个实例。这样实现的原型模式 和 new 比起来有诸多优势,
1)通过拷贝创建对象时,不用关心实例来自哪个类型,只要在例子中实现了拷贝自己的方法即可。
2)通过 new 新建对象时,每个值都是初始状态的,对各个属性,还需要赋值处理。而原型模式在新建了对象时候,就直接获取了原来对象的值,如果有个别属性不一样修改了即可。
通过上面那种方式实现的原型模式,只是在编程方面更灵活。编程效率更高而已,对于执行效果而言,和不用效果是一样的。因为这样实现的模式也是通过new来创建的。有没有更好的克隆方式呢,java 语言里面是有这样的方式的。接下来就介绍 java 实现的 clone 方式。
public class ProtoType implements Cloneable { //......属性值省略 @Override public ProtoType clone(){ ProtoType pro = null; try{ pro = (ProtoType)super.clone(); } catch(CloneNotSupportedException ex){ ex.printStackTrace(); } return pro; } }
上面的代码就是用 java 自己的 clone 实现的克隆。方式就是在 Object 里面有一个 clone 方法,就是用来拷贝对象的,只是使用这个函数需要实现 Cloneable 接口,然后再重写这个方法。为了更好的理解 clone 用法,我们来改造上面的例子。通过 java 语言提供的 clone 来完成这个例子。只是把关键代码贴出来,就是把 原来例子中的 cloneself() 替换成 clone() 。
用 java 自带 clone 改造例子
1 public class Product implements Cloneable { 2 3 private String proID; 4 5 private String proName; 6 7 private String proDescption; 8 9 public String getProID() { 10 return proID; 11 } 12 13 public void setProID(String proID) { 14 this.proID = proID; 15 } 16 17 public String getProName() { 18 return proName; 19 } 20 21 public void setProName(String proName) { 22 this.proName = proName; 23 } 24 25 public String getProDescption() { 26 return proDescption; 27 } 28 29 public void setProDescption(String proDescption) { 30 this.proDescption = proDescption; 31 } 32 33 public void showPro(){ 34 System.out.println("ID:"+this.getProID()+";Name:"+this.getProName()+";Descrip:"+this.getProDescption()); 35 System.out.println("-------------------------------------"); 36 } 37 38 //关键方法 39 // public Product cloneself(){ 40 // Product p = new Product(); 41 // p.setProName(this.proID); 42 // p.setProID(this.proID); 43 // p.setProDescption(this.proDescption); 44 // 45 // return p; 46 // } 47 48 //关键方法 49 @Override 50 public Product clone(){ 51 Product pro = null; 52 try{ 53 pro = (Product)super.clone(); 54 } 55 catch(CloneNotSupportedException ex){ 56 ex.printStackTrace(); 57 } 58 59 return pro; 60 } 61 }
通过 java 语言提供的 clone 方法来复制效率很高,主要有一下原因:
1)不用执行构造函数。
2)原型模式是在内存二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点。
浅拷贝和深拷贝
浅拷贝:就是只拷贝基本类型的值,比如 int、String 等类型。(这里把 String 类型当成了基本类型)。
深拷贝:除了浅拷贝的值外,还需要拷贝引用类型的对象。
1 public class Product2 implements Cloneable { 2 private String name; 3 4 private ArrayList<String> list = new ArrayList<String>(); 5 6 public String getName() { 7 return name; 8 } 9 10 public void setName(String name) { 11 this.name = name; 12 } 13 14 public ArrayList<String> getList() { 15 return list; 16 } 17 18 public void setList(String str) { 19 this.list.add(str); 20 } 21 22 @Override 23 public Product2 clone(){ 24 Product2 pro = null; 25 try{ 26 pro = (Product2)super.clone(); 27 } 28 catch(CloneNotSupportedException ex){ 29 ex.printStackTrace(); 30 } 31 32 return pro; 33 } 34 35 public void show(){ 36 System.out.println("name="+this.name+" list="+this.list); 37 } 38 } 39 40 public class Client { 41 public static void main(String[] args) { 42 Product2 pp = new Product2(); 43 pp.setName("pro2"); 44 pp.setList("pwq"); 45 pp.show(); 46 47 Product2 pp2 = pp.clone(); 48 pp2.setName("pro222"); 49 pp2.setList("pcf"); 50 pp2.show(); 51 } 52 } 53 54 /*****执行效果**********/ 55 name=pro2 list=[pwq] 56 name=pro222 list=[pwq, pcf]
比较两次的执行效果,克隆后的类名字是分离了,就是成功克隆了;但是 list 这个很明显就是共享了,就是没有克隆成功。这个就是浅克隆。如果需要深克隆,就需要修改下上面代码;
1 @SuppressWarnings("unchecked") 2 @Override 3 public Product2 clone(){ 4 Product2 pro = null; 5 try{ 6 pro = (Product2)super.clone(); 7 pro.list = (ArrayList<String>)pro.list.clone(); 8 } 9 catch(CloneNotSupportedException ex){ 10 ex.printStackTrace(); 11 } 12 13 return pro; 14 }
加上这句话后,修改为深度拷贝。手动编写的类也是典型的引用类型,深拷贝也是需要重点注意的。
原型管理器
有时候原型可能有多个,而且还不固定,中间可能动态的变化。这个时候对原型的管理就需要维护一个注册表,这个注册表就是原型管理器。在原型注册器中可以添加和销毁。看例子理解
public interface ProtoType { public String getName(); public void setName(String name); public ProtoType clone(); } public class ConcreteProtoTypeA implements ProtoType{ private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public ProtoType clone(){ ConcreteProtoTypeA cA = new ConcreteProtoTypeA(); cA.setName(this.name); return cA; } } public class ConcreteProtoTypeB implements ProtoType{ //.......... } //原型管理器 public class ProtoTypeManager { private static Map<String,ProtoType> pt = new HashMap<String,ProtoType>(); //不让实例化 private ProtoTypeManager(){} public static synchronized void setProtoType(String protoID,ProtoType p){ pt.put(protoID, p); } public static synchronized void removeProtoType(String protoID){ pt.remove(protoID); } public static synchronized ProtoType getProtoType(String protoID){ ProtoType p = null; if(pt.containsKey(protoID)){ p = pt.get(protoID); } return p; } public static void show(){ System.out.println(pt); } }
通过这个例子就很清楚的理解了原型管理器是有什么用处了,本质上就是存放多个原型的,并且能够动态的添加、删除、获取。
使用场景
1、需要一个类实例化大量的重复对象,或者数据重复性很大,极个别需要修改的属性。
2、对象初始化过程比较复杂。
3、在运行时刻不方便获取原型的类时,也可以通过原型模式来实现。
小结
原型模式说了这么多,本质就是原型不需要获取到所属种类,通过原型就能够通过克隆自己来创建对象。通过这个本质就能看到引出的优势。而 java 又对这个模式进行了语言级别的支持。Object 的 clone 就能够克隆,只要实现了 cloneable 接口。java 自带的 clone 克隆自己还不需要再次执行构造方法,操作的是内存二进制数据,效率非常的好。