• 设计模式(五)原型模式 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 的优势:
    1. 不依赖于某一种带有风险的,语言之外的对象创建机制(clone 是 native 方法)。
    2. 不会与 final 域的正常使用发生冲突(clone 架构与引用可变对象的 final 域的正常使用是不兼容的)。
    3. 不会抛出受检异常。
    4. 不需要类型转换。
    • 《Effective Java》 第11条:谨慎地覆盖 clone

      鉴于 clone() 方法存在这么多限制,《Effective Java》明确指出:

      除了拷贝数组,其他任何情况都不应该去覆盖 clone() 方法,也不该去调用它。

    • 关于深复制:

      这篇文章 第004弹:几种通用的深度复制的方法 介绍了几种深复制的通用方法。

  • 相关阅读:
    sql in not in 案例用 exists not exists 代替
    根据算法规则进行匹配相似车辆
    随机生成临时车牌号
    无法加载 DLL“ParkCOM.dll”: 找不到指定的模块。 (异常来自 HRESULT:0x8007007E) 终结者
    c# 除掉前三个字符,剩下的4个字符全为数字方为特殊车辆
    UI设计文本框解决Placeholder的在IE10 以下 IE 9 IE8 IE 7 的兼容问题
    EF框架 对字段属性为NULL的空值处理 类型前面加上?保证EF列表读取显示数据不会报异常
    boost::property_tree读取解析.xml文件
    C++ URLencode library
    http与中文编码传输
  • 原文地址:https://www.cnblogs.com/jing-an-feng-shao/p/7594680.html
Copyright © 2020-2023  润新知