原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。也属于创建型模式。就是使用原型实例指定待创建对象的类型,并且通过复制这个原型来创建新的对象。
这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。原型模式可以让我们不通过new来初始化对象,避免了构造函数的约束,提升了系统性能,但是也增加了系统的代码复杂性。
代码实现:
一、通过原型对象的copy方法复制一个新对象
1.创建一个接口,该接口中必须有一个可以复制本身的方法
packagecom.xing.design.prototype; /*** 原型* @author xing*/publicinterfacePrototype { /*** 复制本对象* @return*/Prototypecopy(); } 2.创建原型对象packagecom.xing.design.prototype; importjava.util.ArrayList; importjava.util.List; /*** 原型拷贝* @author xing*/publicclassPrototypeCopyimplementsPrototype{ /*** 存放着必定不变的东西,也可向里面添加新东西*/privateList<String>strList; publicPrototypeCopy() { strList=newArrayList<>(); } publicPrototypeCopy(List<String>strList) { this.strList=strList; } /*** 添加新东西操作* @param str*/publicvoidaddStrToList(Stringstr) { strList.add(str); } publicPrototypecopy() { List<String>strLists=newArrayList<>(strList); returnnewPrototypeCopy(strLists); } publicStringtoString() { return"PrototypeCopy [strList="+strList+"]"; } }
3.编写main方法测试
packagecom.xing.design.prototype; importjava.util.ArrayList; importjava.util.List; /*** 客户端* @author xing*/publicclassPrototypeDemo { publicstaticvoidmain(String[] args) { List<String>strList=newArrayList<>(); strList.add("不变的东西"); strList.add("复制的对象里也总有他们"); // 这里用他做原型,每一个复制的对象都会有上面这两句话,就假设他们是消耗资源的操作PrototypeCopyprototype=newPrototypeCopy(strList); // 复制一个PrototypeCopyprototypeCopy1= (PrototypeCopy) prototype.copy(); // 再复制一个System.out.println("复制一个:"+prototypeCopy1); PrototypeCopyprototypeCopy2= (PrototypeCopy) prototype.copy(); prototypeCopy2.addStrToList("自定义新加的"); System.out.println("再复制一个:"+prototypeCopy2); // 基于原型的复制才意义哈,用谁来复制 谁就是原型PrototypeCopyprototypeCopy3= (PrototypeCopy) prototypeCopy2.copy(); System.out.println("再拿复制出来的复制一个:"+prototypeCopy3); } }
4.测试结果
以上结果可以看到,我们没有通过new关键字来获取对象,而是通过原型对象的copy方法成功复制了一个对象。
二、Java 的 Object 的clone()方法
Java中lang包下的万类之王Object提供了一个clone()方法。
clone()方法还是个native方法,native方法就是非Java语言实现的代码,供Java程序调用的,因为Java程序是运行在JVM虚拟机上面的,要想访问到比较底层的与操作系统相关的就没办法了,只能由靠近操作系统的语言来实现。
Object.clone()方法使用过程:
被复制的类(也就是原型类)需要实现Clonenable接口(不实现的话在调用clone方法会抛出CloneNotSupportedException异常), 该接口为标记接口(不含任何方法),就是标记说这个类是可以复制的。
重写覆盖clone()方法,访问修饰符设为public,在方法中调用super.clone()方法得到需要的复制对象。
代码实现:
1.编写原型类
packagecom.xing.design.prototype; importjava.util.ArrayList; importjava.util.List; publicclassPrototypeCopyObjimplementsCloneable{ privateStringstr; privateList<String>strList; publicPrototypeCopyObj() { strList=newArrayList<>(); } publicPrototypeCopyObj(List<String>strList) { this.strList=strList; } publicvoidaddStrToList(Stringstr) { strList.add(str); } publicObjectclone() { PrototypeCopyObjobj=null; try{ obj= (PrototypeCopyObj)super.clone(); }catch(CloneNotSupportedExceptione) { e.printStackTrace(); } returnobj; } publicStringgetStr() { returnstr; } publicvoidsetStr(Stringstr) { this.str=str; } publicStringtoString() { return"[strList="+strList+"]"; } }
2.编写main方法测试:
packagecom.xing.design.prototype; /*** 客户端* @author xing*/publicclassPrototypeDemo { publicstaticvoidmain(String[] args) { // 使用Object 的clone()方法PrototypeCopyObjprototypeCopyObj=newPrototypeCopyObj(); prototypeCopyObj.addStrToList("hello"); prototypeCopyObj.addStrToList("str"); System.out.println("prototypeCopyObj:"+prototypeCopyObj); PrototypeCopyObjprototypeCopyObj2= (PrototypeCopyObj) prototypeCopyObj.clone(); prototypeCopyObj2.addStrToList("加了一个"); System.out.println("prototypeCopyObj2:"+prototypeCopyObj2); System.out.println("两个对象比较结果:"+(prototypeCopyObj.hashCode() ==prototypeCopyObj2.hashCode())); System.out.println("prototypeCopyObj的hashCode:"+prototypeCopyObj.hashCode()); System.out.println("prototypeCopyObj2的hashCode:"+prototypeCopyObj2.hashCode()); } }
3.测试结果:
但是这样,其实只是实现了浅拷贝。浅拷贝就是只针对普通的类型,涉及到引用类型就会出现改变新对象的引用属性同时影响到了原型对象的引用属性,这是因为浅拷贝只拷贝了引用类型的地址,并没有去新创空间。
三、验证浅拷贝时引用类型只拷贝了地址的问题
1.新建类作为原型对象的引用属性
packagecom.xing.design.prototype; /*** 原型的引用对象* @author xing*/publicclassPrototypeOther { /*** 存放着必定不变的东西,也可向里面添加新东西*/privateStringstr; publicStringgetStr() { returnstr; } publicvoidsetStr(Stringstr) { this.str=str; } publicStringtoString() { return"tr= "+str; } } publicclassPrototypeCopyObjimplementsCloneable{ privateStringstr; privateList<String>strList; privatePrototypeOtherprototypeOther; publicPrototypeOthergetPrototypeOther() { returnprototypeOther; } publicvoidsetPrototypeOther(PrototypeOtherprototypeOther) { this.prototypeOther=prototypeOther; } ...
2.编写main方法测试
packagecom.xing.design.prototype; /*** 客户端* @author xing*/publicclassPrototypeDemo { publicstaticvoidmain(String[] args) { // 使用Object 的clone()方法PrototypeCopyObjprototypeCopyObj=newPrototypeCopyObj(); prototypeCopyObj.addStrToList("hello"); prototypeCopyObj.addStrToList("str"); System.out.println("prototypeCopyObj:"+prototypeCopyObj); // 测试浅拷贝弊端PrototypeOtherprototypeOther=newPrototypeOther(); prototypeOther.setStr("浅拷贝弊端就是引用类型只会改变地址,不会新创空间"); prototypeCopyObj.setPrototypeOther(prototypeOther); PrototypeCopyObjprototypeCopyObj3= (PrototypeCopyObj) prototypeCopyObj.clone(); System.out.println("浅拷贝后引用类型的值:"+prototypeCopyObj3.getPrototypeOther().getStr()); prototypeCopyObj3.getPrototypeOther().setStr("弊端那句话改变"); System.out.println("原型引用类型的值也变成了新对象的值:"+prototypeCopyObj.getPrototypeOther().getStr()); } }
3.测试结果:
解决这个问题,就要使用深拷贝。
四、深拷贝
深拷贝就是将引用类型的那个类也变成可复制的类。
1.实现cloneable接口并重写clone()方法:
packagecom.xing.design.prototype; /*** 原型的引用对象* @author xing*/publicclassPrototypeOtherimplementsCloneable{ /*** 存放着必定不变的东西,也可向里面添加新东西*/privateStringstr; publicStringgetStr() { returnstr; } publicvoidsetStr(Stringstr) { this.str=str; } publicObjectclone() { PrototypeOtherobj=null; try{ obj= (PrototypeOther)super.clone(); }catch(CloneNotSupportedExceptione) { e.printStackTrace(); } returnobj; } publicStringtoString() { return"tr= "+str; } }
2.在原型类中的clone方法中加上深拷贝那句:
publicObjectclone() { PrototypeCopyObjobj=null; // 浅复制try{ obj= (PrototypeCopyObj)super.clone(); }catch(CloneNotSupportedExceptione) { e.printStackTrace(); } // 深复制obj.prototypeOther= (PrototypeOther) prototypeOther.clone(); returnobj; }
3.编写测试:
packagecom.xing.design.prototype; /*** 客户端* @author xing*/publicclassPrototypeDemo { publicstaticvoidmain(String[] args) { // 使用Object 的clone()方法PrototypeCopyObjprototypeCopyObj=newPrototypeCopyObj(); prototypeCopyObj.addStrToList("hello"); prototypeCopyObj.addStrToList("str"); System.out.println("prototypeCopyObj:"+prototypeCopyObj); // 测试浅拷贝弊端 及深拷贝PrototypeOtherprototypeOther=newPrototypeOther(); prototypeOther.setStr("浅拷贝弊端就是引用类型只会改变地址,不会新创空间"); prototypeCopyObj.setPrototypeOther(prototypeOther); PrototypeCopyObjprototypeCopyObj3= (PrototypeCopyObj) prototypeCopyObj.clone(); System.out.println("拷贝后引用类型的值:"+prototypeCopyObj3.getPrototypeOther().getStr()); prototypeCopyObj3.getPrototypeOther().setStr("弊端那句话改变"); System.out.println("新对象引用类型的值:"+prototypeCopyObj3.getPrototypeOther().getStr()); // System.out.println("原型对象引用类型的值也变成了新对象的值:"+prototypeCopyObj.getPrototypeOther().getStr());System.out.println("原型对象引用类型的值还是原来的值:"+prototypeCopyObj.getPrototypeOther().getStr()); } }
4.运行结果:
OK!浅拷贝实现 Cloneable,重写clone()方法,深拷贝是通过实现 Serializable 读取二进制流。
总结:
与通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。逃避了构造函数的约束。在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。
END