上一篇咱们学习的是建造者模式,有的小伙伴会觉得比较复杂和难以理解。今天咱们来学习一个简单的设计模式-原型模型。
原型模式的核心就是克隆对象,将一个对象作为拷贝对象(原型),然后不断的创建出新的对象。
原型模式的应用场景还是比较多的,例如Spring框架中@Scope("prototype"),项目中数据模型相似的不同业务订单,OA系统中的日报、周报等。
这里以印钞机为例,印钞机在印刷"软妹币"时,面额和材料相同,但是编号不同,这时候我们就可以使用原型模式。
第一步:创建软妹币对象并实现Cloneable接口
/** * 软妹币对象 */public class RMB implements Cloneable { private int denomination; private int code; public RMB() { System.out.println("调用软妹币构造函数"); } public int getDenomination() { return denomination; } public void setDenomination(int denomination) { this.denomination = denomination; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } @Override protected RMB clone() throws CloneNotSupportedException { return (RMB) super.clone(); } @Override public String toString() { return "RMB{" + "denomination=" + denomination + ", code=" + code + '}'; } }12345678910111213141516171819202122232425262728293031323334353637383940414243复制代码类型:[java]
第二步:创建测试类并查看结果
public class Test { public static void main(String[] args) throws CloneNotSupportedException { RMB rmb1 = new RMB(); rmb1.setDenomination(100); rmb1.setCode(1); RMB rmb2 = rmb1.clone(); rmb2.setCode(2); System.out.println("RMB: " + rmb1 + " hashcode:" + rmb1.hashCode()); System.out.println("RMB: " + rmb2 + " hashcode:" + rmb2.hashCode()); } }1234567891011121314复制代码类型:[java]
测试结果:
调用软妹币构造函数 RMB: RMB{denomination=100, code=1} hashcode:1239731077RMB: RMB{denomination=100, code=2} hashcode:557041912123复制代码类型:[java]
ps:到这里其实就完成了原型模式的落地了。这里需要注意的是原型对象需要实现Cloneable重写clone方法。如果不实现Cloneable接口但调用super.clone()会抛出异常。这个是Java早期设计上的问题,实现Cloneable只是标记当前类可以使用clone方法而已。
上面软妹币对象中的属性都是简单的数据类型,如果这时候需要再加上操作员的信息呢?咱们来改造下上面的代码看看会出现什么效果。
第一步:创建操作员
/** * 操作员对象 只保留name属性便于演示 */public class Operator { private String name; public Operator() { } public Operator(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Operator{" + "name='" + name + '\'' + '}'; } }1234567891011121314151617181920212223242526272829复制代码类型:[java]
第二步:修改软妹币对象
/** * 软妹币对象 */public class RMB implements Cloneable { private int denomination; private int code; private Operator operator; public RMB() { System.out.println("调用软妹币构造函数"); } public int getDenomination() { return denomination; } public void setDenomination(int denomination) { this.denomination = denomination; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public Operator getOperator() { return operator; } public void setOperator(Operator operator) { this.operator = operator; } @Override protected RMB clone() throws CloneNotSupportedException { return (RMB) super.clone(); } @Override public String toString() { return "RMB{" + "denomination=" + denomination + ", code=" + code + ", operator=" + operator + '}'; } }1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253复制代码类型:[java]
第三步:创建测试类并查看结果
public class Test { public static void main(String[] args) throws CloneNotSupportedException { RMB rmb1 = new RMB(); rmb1.setDenomination(100); rmb1.setCode(1); rmb1.setOperator(new Operator("张三")); RMB rmb2 = rmb1.clone(); rmb2.setCode(2); System.out.println("RMB: " + rmb1 + " operator:" + rmb1.getOperator().hashCode()); System.out.println("RMB: " + rmb2 + " operator:" + rmb2.getOperator().hashCode()); } }123456789101112131415复制代码类型:[java]
测试结果:
调用软妹币构造函数 RMB: RMB{denomination=100, code=1, operator=Operator{name='张三'}} operator:557041912RMB: RMB{denomination=100, code=2, operator=Operator{name='张三'}} operator:557041912123复制代码类型:[java]
ps:根据测试结果,我们可以看出operator对象是同一个,这个是因为clone方法使用的是浅拷贝。
浅拷贝:克隆对象中所有的成员变量与原型对象的值相等,引用类型变量和克隆对象指向同一个对象。
深拷贝:无论原型对象的成员变量是基本数据类型还是引用类型,都将复制一份给克隆对象。
显然,在很多场景下浅拷贝不满足我们的需求。
方案一:
第一步:让Operator实现Cloneable接口
/** * 操作员对象 只保留name属性便于演示 */public class Operator implements Cloneable { private String name; public Operator() { } public Operator(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override protected Operator clone() throws CloneNotSupportedException { return (Operator) super.clone(); } @Override public String toString() { return "Operator{" + "name='" + name + '\'' + '}'; } }1234567891011121314151617181920212223242526272829303132333435复制代码类型:[java]
第二步:重写RMB中的clone方法
/** * 软妹币对象 */public class RMB implements Cloneable { private int denomination; private int code; private Operator operator; public RMB() { System.out.println("调用软妹币构造函数"); } public int getDenomination() { return denomination; } public void setDenomination(int denomination) { this.denomination = denomination; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public Operator getOperator() { return operator; } public void setOperator(Operator operator) { this.operator = operator; } @Override protected RMB clone() throws CloneNotSupportedException { RMB rmb = (RMB) super.clone(); rmb.setOperator(this.getOperator().clone()); return rmb; } @Override public String toString() { return "RMB{" + "denomination=" + denomination + ", code=" + code + ", operator=" + operator + '}'; } }1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556复制代码类型:[java]
第三步:查看测试结果
调用软妹币构造函数 RMB: RMB{denomination=100, code=1, operator=Operator{name='张三'}} operator:557041912RMB: RMB{denomination=100, code=2, operator=Operator{name='张三'}} operator:1134712904123复制代码类型:[java]
ps:根据测试结果,operator对象已经是两个对象了。
方案二:
第一步:让Operator实现Serializable接口
import java.io.Serializable;/** * 操作员对象 只保留name属性便于演示 */public class Operator implements Serializable { private String name; public Operator() { } public Operator(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Operator{" + "name='" + name + '\'' + '}'; } }12345678910111213141516171819202122232425262728293031复制代码类型:[java]
第二步:让RMB实现Serializable接口,并自定义深度拷贝方法
import java.io.*;/** * 软妹币对象 */public class RMB implements Cloneable, Serializable { private int denomination; private int code; private Operator operator; public RMB() { System.out.println("调用软妹币构造函数"); } public int getDenomination() { return denomination; } public void setDenomination(int denomination) { this.denomination = denomination; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public Operator getOperator() { return operator; } public void setOperator(Operator operator) { this.operator = operator; } @Override protected RMB clone() throws CloneNotSupportedException { return (RMB) super.clone(); } public RMB deepClone() { try { // 序列化 ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(this); // 反序列化 ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); return (RMB) ois.readObject(); } catch (Exception e) { e.printStackTrace(); return null; } } @Override public String toString() { return "RMB{" + "denomination=" + denomination + ", code=" + code + ", operator=" + operator + '}'; } }1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374复制代码类型:[java]
第三步:修改测试类并查看结果
public class Test { public static void main(String[] args) throws CloneNotSupportedException { RMB rmb1 = new RMB(); rmb1.setDenomination(100); rmb1.setCode(1); rmb1.setOperator(new Operator("张三")); // 调用深度克隆方法 RMB rmb2 = rmb1.deepClone(); rmb2.setCode(2); System.out.println("RMB: " + rmb1 + " operator:" + rmb1.getOperator().hashCode()); System.out.println("RMB: " + rmb2 + " operator:" + rmb2.getOperator().hashCode()); } }12345678910111213141516复制代码类型:[java]
测试结果:
调用软妹币构造函数 RMB: RMB{denomination=100, code=1, operator=Operator{name='张三'}} operator:863831416RMB: RMB{denomination=100, code=2, operator=Operator{name='张三'}} operator:1836643189123复制代码类型:[java]
ps:根据测试结果,operator对象也是两个不同的对象。
优点:
用法简单,上手难度低
提高创建新对象的效率
可实现不同时刻对象状态(如导入昨天日报)
缺点:
必须要实现Cloneable接口重写clone方法
违背开闭原则
深拷贝实现相对复杂
引用类型如果是多层嵌套,则每一层对象都需要支持克隆
联系客服