• 设计模式(四)建造者模式 Builder


    • Builder:

      《Effective Java》 第2条:遇到多个构造器参数时要考虑用构建器。

      建造者模式(Builder Pattern),也称生成器模式,定义如下:

      将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

      Separate the construction of a complex object from its representation so that the same construction process can create different representation.

    • 什么时候使用 Builder:

      我们常常会看见这种情况:我需要一个初始化一个复杂对象,在初始化的同时完成参数的赋值工作。

    • 数据模型:
    @Data
    @EqualsAndHashCode
    @ToString
    public class PolicyCommon {
    
        private String code;
        private String category;
        private String name;
        private String inceptionDate;
    
        public PolicyCommon() {
            super();
        }
    
        public PolicyCommon(String code, String category, String name) {
            super();
            this.code = code;
            this.category = category;
            this.name = name;
        }
    }
    • 模型分析:

      PolicyCommon 带有4个参数,假定只有 inceptionDate不需要在创建的时候赋值。

      剩余3个参数参与初始化的工作,那么这个对象初始化之后的状态可能性为:2^3=8。

    • 传统的解决方案1——使用无参构造器,然后依次调用 set() 方法:
        @Test
        void testPolicyCommon1() {
            PolicyCommon policy = new PolicyCommon();
            policy.setName("Gerrard");
            policy.setCode("11123768");
            policy.setCategory("Engineer");
            System.out.println(policy);
        }

      这种方法,将构造对象的过程拆分成多个动作,是存在风险的。

      因为对象不是一次性构造完成,使得对象在构造过程中存在状态不一致的情况。

      期间有 this 指针溢出的风险,阻碍了这个对象称为不可变对象的可能。在多线程条件下,需要额外的工作才能保证线程安全。

    • 传统的解决方案2——使用重叠参数的构造器:
        @Test
        void testPolicyCommon2() {
            PolicyCommon policy = new PolicyCommon("11123768", "Engineer", "Gerrard");
            System.out.println(policy);
        }

      这样做的劣势在于:

    1. 从调用者的角度来说,属性的意义不明显,很容易将参数位置颠倒了,但是编译器并没有报错,例如: new PolicyCommon("Engineer", "Gerrard", "11123768");
    2. 如果我只想初始化一个属性,如 code,那么构造器的调用会有许多冗余参数。例如:new PolicyCommon("11123768", null, null);
    •  Builder 给出的解决方案:

      使用一个静态内部类 Builder,Builder 持有需要动态生成的属性。

      Builder 为每一个属性,提供 set() 方法,但是与通常的set() 方法不同,这些 set() 方法的返回值是 Builder 本身。

      Builder 提供一个 build() 方法,返回类型为外部类。

      外部类中,提供一个参数为 Builder 对象的构造器,且将其的权限设置为 private,如此一来,构造器的唯一途径就是 build() 方法。

    • 代码:
    @Data
    @EqualsAndHashCode
    @ToString
    public class PolicyBuilder {
    
        private String code;
        private String category;
        private String name;
        private String inceptionDate;
    
        private PolicyBuilder(Builder builder) {
            code = builder.code;
            category = builder.category;
            name = builder.name;
        }
    
        public static class Builder {
    
            private String code;
            private String category;
            private String name;
    
            public Builder setCode(String code) {
                this.code = code;
                return this;
            }
    
            public Builder setCategory(String category) {
                this.category = category;
                return this;
            }
    
            public Builder setName(String name) {
                this.name = name;
                return this;
            }
    
            public PolicyBuilder build() {
                return new PolicyBuilder(this);
            }
        }
    }
    • 调用 Builder:
        @Test
        void testPolicyBuilder() {
            PolicyBuilder policy = new PolicyBuilder.Builder().setName("Gerrard").setCode("11123768").build();
            System.out.println(policy);
        }

      可以看出,Builder 初始化的过程中,对参数选择更加自由,而且它不会使对象处于不同的状态。

      劣势在于冗长的内部类代码,以及构建过程中会增加一个 Builder 对象。

    • 使用 Lombok 一键 Builder:

      细心的朋友,在最初的 PolicyCommon 的数据模型中,会发现三个注解,这是 Lombok 框架的功能(org.projectlombok,License = MIT)。

      @Data,为类的每一个属性,自动生成 get() 和 set() 方法。

      @EqualsAndHashCode,为类生成 equals() 和 hashCode() 方法。

      @ToString,为类生成 toString() 方法。

      除此之外,Lombok 还提供了 @Builder 的注解,自动为类提供了 Builder 的功能。

    • Lombok Builder:
    @Data
    @EqualsAndHashCode
    @ToString
    public class PolicyBuilderLombok {
    
        private String code;
        private String category;
        private String name;
        private String inceptionDate;
    
        @Builder
        public PolicyBuilderLombok(String code, String category, String name) {
            super();
            this.code = code;
            this.category = category;
            this.name = name;
        }
    }
        @Test
        void testPolicyBuilderLombok() {
            PolicyBuilderLombok policy = new PolicyBuilderLombok.PolicyBuilderLombokBuilder()
                    .name("Gerrard").code("11123768").build();
            System.out.println(policy);
        }
    • 解析 @Builder:

      检查元注解:@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR})

      这表明这个注解可以加在:类、构造器、方法(不推荐加在方法上)。

      @Builder 加在类上 <==> ,在类的全参构造器上加上 @Builder。

      @Builder 加在构造器上,这个构造器的入参就是内部类 Builder 的动态参数。

  • 相关阅读:
    上传图片2(接上一篇)
    上传图片2
    上传图片
    SQL Server基础之存储过程
    C#导入Excel、Excel导入、导入.xls 、导入.xlsx、Excel2003版本、Excel2007版本
    C#使用 System.Net.Mail发送邮件功能
    C#发送邮件三种方法,Localhost,SMTP,SSL-SMTP
    angular模板
    从“繁”到“简”进行数组去重
    CSS强制英文、中文换行与不换行
  • 原文地址:https://www.cnblogs.com/jing-an-feng-shao/p/7552615.html
Copyright © 2020-2023  润新知