2、6 原型模式
定义
1)用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
2)这种不通过 new 关键字来产生一个对象,而是通过对象复制来实现的模式就叫做原型模式。
3)原型模式先产生一个包含大量共用信息的类,然后拷贝出副本,修正细节信息,建立了一个完整的个性对象。
4)一个对象的产生可以不由零起步,直接从一个已经具备一定雏形的对象克隆,然后再修改为生产需要的对象。
5)原型模式其实就是从一个对象再创建另外一个可定制的对象,而不需要知道任何创建的细节。
6)一般在初始化的信息不发生变化的情况下,克隆是最好的办法。这既隐藏了对象创建的细节,又对性能是大大的提高(相对每次 new 来说)。
7)不用初始化对象,而是动态地获得对象运行时的状态。
8)当创建给定类的实例的过程很昂贵或很复杂时,就使用原型模式。
9)原型模式允许你通过复制现有的实例来创建新的实例(在 Java 中,这通常意味着使用 clone()方法,或者反序列化)。这个模式的重点在于,客户的代码在不知道要实例化何种特定类的情况下,可以制造出新的实例。
通用类图
原型模式的核心是一个 clone 方法,通过该方法进行对象的拷贝,Java 提供了一个 Cloneable 接口来标识这个对象是可拷贝的。这个接口只是一个标记作用,这个 JVM 中具有这个标记的对象才有可能被拷贝。覆盖/重写 clone()方法才能从”有可能被拷贝”转换为”可以被拷贝”。
通用源码
public class Client1 { } class PrototypeClass implements Cloneable{ @Override protected Object clone() throws CloneNotSupportedException { PrototypeClass prototypeClass = null; try { prototypeClass = (PrototypeClass)super.clone(); }catch (Exception e){ e.printStackTrace(); } return prototypeClass; } }
优点缺点
1)性能优良:原型模式是在内存二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点。
2)逃避构造函数的约束
这既是它的优点也是缺点,直接在内存中拷贝,构造函数是不会执行的。优点就是减少了约束,缺点也是减少了约束,需要在实际应用时考虑。
3)向客户隐藏制造新实例的复杂性。
4)提供让客户能够产生未知类型对象的选项。
5)在某些环境下,复制对象比创建新对象更有效。
使用原型模式的缺点:对象的复制有时相当复杂。
适用场景
1)资源优化场景
类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。
2)性能和安全要求的场景
通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。
3)一个对象多个修改者的场景
一个对象需要提供给其他对象访问,而且多个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
4)线程不安全也可以通过对象的复制功能来解决这个问题。
5)在一个复杂的类层次中,当系统必须从其中的许多类型创建新对象时,可以考虑原型。
在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过clone的方法创建一个对象,然后由工厂方法提供给调用者。
注意事项
构造函数不会被执行
案例
public class Client2 { public static void main(String[] args) { //产生一个对象 System.out.println("1111111111111111"); Thing thing = new Thing(); System.out.println("2222222222222222"); //拷贝一个对象 System.out.println("3333333333333333"); Thing cloneThing = thing.clone(); System.out.println("4444444444444444"); } } class Thing implements Cloneable{ public Thing() { System.out.println("构造函数被执行"); } @Override public Thing clone() { Thing thing = null; try { thing = (Thing)super.clone(); }catch (Exception e){ e.printStackTrace(); } return thing; } } 1111111111111111 构造函数被执行 2222222222222222 3333333333333333 4444444444444444
描述
对象拷贝时构造函数确实没有被执行,因为 Object 类的 clone 方法的原理是从内存中(具体来说是从堆内存)以二进制流的方式进行拷贝,重新分配一个内存块,所以构造函数没有被执行也是非常正常的了。
浅拷贝
案例
import java.util.ArrayList; import java.util.List; public class Client3 { public static void main(String[] args) { //产生一个对象 Thing thing = new Thing(); thing.setValue("zhangsan"); thing.setNum1(11); thing.setNum2(12); CloneModel cloneModel1 = new CloneModel(); cloneModel1.setStrtest("strtestval1"); thing.setCloneModel(cloneModel1); thing.setStr1("str11val"); //拷贝一个对象 Thing cloneThing1 = thing.clone(); cloneThing1.setValue("lisi.wangwu."); cloneThing1.setNum1(21); cloneThing1.setNum2(22); CloneModel cloneModel2 = new CloneModel(); cloneModel2.setStrtest("strtestval2"); cloneThing1.setCloneModel(cloneModel2); cloneThing1.setStr1("str12val"); System.out.println("----------clone-before-----------"); System.out.println(thing.getValue()); System.out.println(thing.getNum1()); System.out.println(thing.getNum2()); System.out.println(thing.getStr1()); System.out.println(thing.getCloneModel().getStrtest()); System.out.println("----------clone-after-----------"); System.out.println(cloneThing1.getValue()); System.out.println(cloneThing1.getNum1()); System.out.println(cloneThing1.getNum2()); System.out.println(cloneThing1.getStr1()); System.out.println(cloneThing1.getCloneModel().getStrtest()); } } class Thing implements Cloneable{ private List<String> arrayList = new ArrayList<>(); private CloneModel cloneModel = new CloneModel(); private String str1 = "str1val"; private int num1 = 1; private Integer num2 = 1; @Override public Thing clone() { Thing thing = null; try { thing = (Thing)super.clone(); }catch (Exception e){ e.printStackTrace(); } return thing; } public void setValue(String value){ this.arrayList.add(value); } public List<String> getValue(){ return this.arrayList; } public void setStr1(String str){ this.str1 = str; } public String getStr1(){ return this.str1; } public void setNum1(int num){ this.num1 = num; } public int getNum1(){ return this.num1; } public void setNum2(int num){ this.num2 = num; } public int getNum2(){ return this.num2; } public CloneModel getCloneModel() { return cloneModel; } public void setCloneModel(CloneModel cloneModel) { this.cloneModel = cloneModel; } } class CloneModel{ private String strtest; public String getStrtest() { return strtest; } public void setStrtest(String strtest) { this.strtest = strtest; } } ----------clone-before----------- [zhangsan, lisi.wangwu.] 11 12 str11val strtestval1 ----------clone-after----------- [zhangsan, lisi.wangwu.] 21 22 str12val strtestval2
描述
可以发现案例中输出了 lisi.wangwu。因为 Object 类提供的方法 clone 只是拷贝本对象,其对象内部的数组、引用对象等都不拷贝,还是指向原生对象的内部元素地址,这种拷贝就叫做浅拷贝。(一种非常不安全的方式)
注意:使用原型模式时,引用的成员变量必须满足两个条件才不会被拷贝:
一是类的成员变量,而不是方法内变量;
二是必须是一个可变的引用对象,而不是一个原始类型(比如 int、long、char 等)或不可变对象(String 是没有 clone 方法的,处理机制也比较特殊,通过字符串池 stringpool 在需要的时候才在内存中创建新的字符串。)
深拷贝
案例
import java.util.ArrayList; import java.util.List; public class Client4 { public static void main(String[] args) { //产生一个对象 Thing thing = new Thing(); thing.setValue("zhangsan"); //拷贝一个对象 Thing cloneThing = thing.clone(); cloneThing.setValue("lisi.wangwu."); System.out.println(thing.getValue()); } } class Thing implements Cloneable{ //这里要用ArrayList而不是List(要指明具体的类型) //这里不要加final(要使用clone方法,类的成员变量上不要增加final关键字,用了final怎能重新赋值?) private ArrayList<String> arrayList = new ArrayList<>(); @Override public Thing clone() { Thing thing = null; try { thing = (Thing)super.clone(); //这里一定要加上thing //注意不会被拷贝的条件:可变的引用对象不会被拷贝。 //因为个人理解上为这个实体的clone和集合的clone本质并不是一体的,虽然在同一个方法体中且在方法内来处理。 thing.arrayList = (ArrayList<String>) this.arrayList.clone(); }catch (Exception e){ e.printStackTrace(); } return thing; } public void setValue(String value){ this.arrayList.add(value); } public List<String> getValue(){ return this.arrayList; } } [zhangsan]
描述
该方法实现了完全的拷贝,两个对象之间没有任何的瓜葛。深拷贝还有一种实现方式就是通过写二进制流来操作对象,然后实现深拷贝。
标题:设计模式(六)创建型模式(对象创建型模式)原型模式-PROTOTYPE
作者:yazong
地址:https://blog.llyweb.com/articles/2020/06/04/1591281200167.html