设计模式(五)原型模式 Prototype
- 原型模式:
原型模式,是指基于一个已经给定的对象,通过拷贝的方式,创建一个新的对象,这个给定对象,就是“原型”。
在 Java 中,原型模式体现为 Object 的 clone() 方法。
所有类都可以通过实现 Cloneable 接口,以及重写 clone() 方法,来实现原型模式。
- 代码:
@Data @Builder @NoArgsConstructor @AllArgsConstructor public class Liability implements Cloneable {private String code; private String name; private String category; private boolean isMajor; @Override protected Liability clone() throws CloneNotSupportedException { return (Liability) super.clone(); } }
@Data @Builder public class PolicyShallowClone implements Cloneable { private String code; private int applicantAge; private Liability liability; private List<String> specialDescriptions; @Override public PolicyShallowClone clone() throws CloneNotSupportedException { return (PolicyShallowClone) super.clone(); } }
- 缩减 clone() 方法的返回类型:
自 JDK1.5 开始,Java 引进了一个新的特性:协变返回类型(covariant return type)。
即:覆盖方法的返回类型,可以是被覆盖方法的返回类型的子类。
所以需要在 clone() 方法内部进行强转。
这体现了一条通则:永远不要让客户去做任何类库能够替客户完成的事情。
- clone() 是一种浅度复制(Shallow Copy):
@Test void testPolicy1() throws Exception { // Build original policy Liability liability = new Liability.LiabilityBuilder().code("0001").name("Liability").category("XPXA").build(); String specialDescription1 = "text1"; String specialDescription2 = "text2"; List<String> specialDescriptions = new ArrayList<>(Arrays.asList(specialDescription1, specialDescription2)); PolicyShallowClone policyA = PolicyShallowClone.builder().specialDescriptions(specialDescriptions).liability(liability).code("code001").applicantAge(18).build(); // Call clone PolicyShallowClone policyB = policyA.clone(); Assertions.assertSame(policyA.getCode(), policyB.getCode()); Assertions.assertEquals(policyA.getCode(), policyB.getCode()); // Assert shallow clone policyA.getSpecialDescriptions().add("text3"); Assertions.assertSame(policyA.getLiability(), policyB.getLiability()); Assertions.assertTrue(policyA.getSpecialDescriptions().size() == policyB.getSpecialDescriptions().size()); }
- 编写一个优秀的 clone() 方法:
克隆对象的数据来源,必须来自于 clone() 方法,所以永远在方法内部调用 super.clone() 方法。
所有的父类必须很好地实现了 clone() 方法。
如果当前类包含的域引用了可变对象,需要递归地调用 clone() 方法。
如果在线程安全的类中实现 Cloneable 接口,clone() 方法必须得到很好的同步。
- 一个深度复制的 clone() 方法:
@Data @Builder public class PolicyDeepClone implements Cloneable { private String code; private int applicantAge; private Liability liability; private List<String> specialDescriptions; @Override public PolicyDeepClone clone() throws CloneNotSupportedException { PolicyDeepClone clone = (PolicyDeepClone) super.clone(); clone.specialDescriptions = new ArrayList<>(this.specialDescriptions); clone.liability = this.liability.clone(); return clone; } }
- 深度复制的测试:
@Test void testPolicy2() throws Exception { // Build original policy Liability liability = new Liability.LiabilityBuilder().code("0001").name("Liability").category("XPXA").build(); String specialDescription1 = "text1"; String specialDescription2 = "text2"; List<String> specialDescriptions = new ArrayList<>(Arrays.asList(specialDescription1, specialDescription2)); PolicyDeepClone policyA = PolicyDeepClone.builder().specialDescriptions(specialDescriptions).liability(liability).code("code001").applicantAge(18).build(); // Call clone PolicyDeepClone policyB = policyA.clone(); // Assert deep clone policyA.getSpecialDescriptions().add("text3"); Assertions.assertNotSame(policyA.getLiability(), policyB.getLiability()); Assertions.assertFalse(policyA.getSpecialDescriptions().size() == policyB.getSpecialDescriptions().size()); }
- 有必要这么复杂吗?
从上述的介绍,我们不难发现,要完成一个优秀的 clone() 方法,存在诸多限制。
并且,当我们实现了 clone() 方法,在编译器中,还会看到一条 Blocker 级别的 Sonar 警告:
Remove this "clone" implementation; use a copy constructor or copy factory instead.
它推荐的是一个拷贝构造器和拷贝工厂。
- 拷贝构造器(Copy constructor)
@Data @Builder public final class PolicyCopyConstructor { private String code; private int applicantAge; private Liability liability; private List<String> specialDescriptions; public PolicyCopyConstructor(PolicyCopyConstructor policy) { this.code = policy.code; this.applicantAge = policy.applicantAge; this.liability = policy.liability; this.specialDescriptions = policy.specialDescriptions; } }
显然,这是一个浅度复制的实现,如果需要深度复制,需要深一步挖掘,这里不详述。
- 拷贝工厂(Copy factory):
@Data public final class PolicyCopyFactory { private String code; private int applicantAge; private Liability liability; private List<String> specialDescriptions; public static PolicyCopyFactory newInstance(PolicyCopyFactory policy) { PolicyCopyFactory copyPolicy = new PolicyCopyFactory(); copyPolicy.setCode(policy.getCode()); copyPolicy.setApplicantAge(policy.getApplicantAge()); copyPolicy.setLiability(policy.getLiability()); copyPolicy.setSpecialDescriptions(policy.getSpecialDescriptions()); return copyPolicy; } }
拷贝工厂本质上使我们之前提到过的静态工厂的一种变形。
在这里,这也是浅度复制的实现。
- Copy constructor & Copy factory 的优势:
- 不依赖于某一种带有风险的,语言之外的对象创建机制(clone 是 native 方法)。
- 不会与 final 域的正常使用发生冲突(clone 架构与引用可变对象的 final 域的正常使用是不兼容的)。
- 不会抛出受检异常。
- 不需要类型转换。
- 《Effective Java》 第11条:谨慎地覆盖 clone
鉴于 clone() 方法存在这么多限制,《Effective Java》明确指出:
除了拷贝数组,其他任何情况都不应该去覆盖 clone() 方法,也不该去调用它。
- 关于深复制:
这篇文章 第004弹:几种通用的深度复制的方法 介绍了几种深复制的通用方法。