设计模式那点事-创建型模式-建造模式(PK-工厂模式)(温故知新-内部类)(源码分析-RabbitMQ的建造模式的使用)(lombok的@Builder注解)

这个专题,我们将要学习一下设计模式。

序:为什么需要建造模式

PS:我们应该还记得工厂方法模式中,工厂方法模式是一个产品等级,而抽象工厂模式是多个产品等级的产品族。下面的例子中:电脑店(工厂方法模式),苹果专卖店(抽象工厂模式)

小悠(谄媚模式):小胖哥哥,我想买一台电脑Product,什么品牌的好呀?
小胖(不屑模式):买电脑?还是自己组装build好,你把电脑的各个零件Parts买回来,自己组装就好了,我可以帮你装的。

小悠(崇拜模式):恩恩,还有我寝室的5个人,她们也想买电脑,小胖哥哥,你也能帮她们吗?

小胖(无语模式):emmm,这样吧,我给你推荐一个电脑店Factory吧。打个电话就可以送货上门的。

小悠(不满模式):哼,你不是说自己组装好吗?

小胖(解释模式):这个电脑店Factory很不错,他们会根据你买笔记本的需求。给你组装不同的电脑的。

  • 电脑零件采购员(Builder)根据你们各自的电脑要求,采购回来不同的电脑零件ConcreteBuilder
  • 组装员(Director)将采购回来的零件组装成电脑。

最后送到你们的手上。

小悠(开心模式):听起来不错,谢谢小胖哥哥。


工厂模式实现:获取对象;
建造模式实现:创建对象;

这个例子好像看起来是工厂模式在起作用,减少用户重复性的组装。把购买-组装过程放到了电脑店中。好像没建造模式什么事情。但是购买零件-组装,购买零件-组装......对象的不同就是购买零件属性的不同,但组装过程是相同的,我们可以共用一个组装过程,达到代码的复用。


什么时候使用建造模式:

  1. 需要生成的产品对象内部结构复杂;
  2. 需要生成的产品对象属性相互依赖;
  3. 需要生成的产品对象使用其他不易得到对象;

1. 什么是建造者模式

建造模式利用一个导演者(Director)和具体建造者(Builder)对象,将所需的零件一个个建造出来,从而建造出完整的产品对象。建造者模式将产品结构和产品零件
的建造过程隐藏起来,把対建造过程进行指挥具体建造者的责任分割开,达成责任划分和封装的目的。

设计模式那点事-创建型模式-建造模式(PK-工厂模式)(温故知新-内部类)(源码分析-RabbitMQ的建造模式的使用)(lombok的@Builder注解)_第1张图片
建造模式的结构

首先我们要明白的是:

  • 抽象建造者角色(Builder)抽象接口,规范产品对象各个组成成分。一般来说,产品对象的属性和建造方法的数目相符。此接口独立于应用程序的业务逻辑。
  • 具体建造者角色(ConcreteBuilder)(1)实现抽象建造者Builder所声明的接口。(2)在建造过程完成后,提供产品实例。

  • 导演者角色(Director)调用具体建造者角色以创建产品对象。

  • 产品角色(Product)产品就是建造的复杂角色。

一般来说,每有一个产品类,就有一个相应的具体建造者类。这些产品应当有一样数目的零件,而每有一个零件就相应地在所有的建造者角色里面有一个建造方法。

2. 建造模式代码分析

建造模式分为两个很重要的部分:

  1. Builder接口:定义了如何构建各个部件。
  2. Director角色:负责整体的构建算法,通常是分步骤的构建产品。

建造模式的核心:在于分离构建算法和具体构造的实现,从而使得构建算法可重用,具体的构造实现可以方便扩展和切换,从而可以灵活地构造出不同的产品对象。


代码书写注意点:

  • 抽象建造者角色抽象方法交给了各个子类实现,为不同的产品定制不同的零件。具体方法自己实现。产品对象有多少属性,抽象建造者一般有多少建造方法。

  • 具体建造者角色:实现抽象方法,定制子类产品。通过引用传递,输出产品实例。

  • 导演者角色:将具体建造者角色传入,开始组装数据。对于用户实时传入的参数construct方法需提供方法入参。

  • 产品角色具体创建者角色中包含的对象,提供set方法,以设置参数。

假设有一个电子杂志系统,定期地向用户的电子邮件信箱发送电子杂志。用户可以通过网页订阅电子杂志,也可以通过网页结束订阅。当客户开始订阅时,系统发送一个电子邮件表示欢迎,当客户结束订阅时,系统发送一个电子邮件表示欢送。本例子就是这个系统负责发送“欢迎”和“欢送”邮件的模块。

简略点:

只有一个Message对象,定制欢送和欢迎的Message对象。

public abstract class AutoMessage {
    //收件地址
    private String toAddress;
    //发件地址
    private String fromAddress;
    //标题
    private String subject;
    //内容
    private String body;
    //发送日期
    private LocalDate sendData;

    //为减少篇幅,get和set方法省略
    
}

1. 创建产品子类

//欢送消息
public class GoodbyeMessage extends AutoMessage {

    public GoodbyeMessage() {
        System.out.println("发送欢送消息");
    }
}

//具体产品的父类
public class WelcomeMessage extends AutoMessage {

    public WelcomeMessage() {
        System.out.println("发送欢迎消息");
    }
}

2. 创建抽象建造者角色
定制子类特有零件和公共零件。


//抽象建造者角色
public abstract class Builder {

    //父类的message对象
    protected AutoMessage message;

    //子类定制信息
    public abstract void buildSubject();

    //子类定制信息
    public abstract void buildBody();

    //产品返回方法
    public abstract AutoMessage getAutoMessage();

    //发件地址--建造方法
    public void buildToAddress(String toAddress) {
        message.setToAddress(toAddress);
    }

    //收件地址 --建造方法
    public void buildFromAddress(String fromAddress) {
        message.setFromAddress(fromAddress);
    }

    //发送时间 -- 建造方法
    public void buildSendData() {
        message.setSendData(LocalDate.now());
    }

}

3. 创建具体建造角色:

  • 实现子类特有零件功能;
  • 建造结束后,返回产品实例;
public class GoodbyeBuilder extends Builder {

    public GoodbyeBuilder() {
        message=new GoodbyeMessage();
    }

    @Override
    public void buildSubject() {
        message.setSubject("欢送标题");
    }

    @Override
    public void buildBody() {
        message.setBody("欢送内容");
    }

    @Override
    public AutoMessage getAutoMessage() {
        return message;
    }
}



//具体建造者角色
public class WelcomeBuilder extends Builder  {

    public WelcomeBuilder() {
        message=new WelcomeMessage();
    }

    @Override
    public void buildSubject() {
        message.setSubject("欢迎标题");
    }

    @Override
    public void buildBody() {
        message.setBody("欢迎内容");
    }

    @Override
    public AutoMessage getAutoMessage() {
        return message;
    }
}

4. 导演者角色:

  • 接收用户实时传递数据;
  • 组装对象;
//导演者角色
public class Director {

    Builder builder;

    public Director(Builder builder) {
        this.builder = builder;
    }

    //调用各个零件的建造方法
    public void construct(String toAddress, String fromAdress) {
        builder.buildToAddress(toAddress);
        builder.buildFromAddress(fromAdress);
        builder.buildSubject();
        builder.buildBody();
        builder.buildSendData();
    }
}

5. 测试代码

//测试方法
public class TestBuilder {

    public static void main(String[] args) {
        Builder builder=new WelcomeBuilder();
        Director director = new Director(builder);
        director.construct("toAddress:123.100.12.11","fromAddress:192.100.12.12");
        //最后的对象是哪个?
        WelcomeMessage autoMessage = (WelcomeMessage) builder.getAutoMessage();
        System.out.println("建造者的消息:"+autoMessage);
    }
}

需要注意:具体产品无需实现同一父类。


3. 建造者模式的变种

其实建造模式的核心就在于导演者角色。所以可以对建造模式进行一定的简化,因为我们使用建造模式就是创造某个复杂对象,因此适当的简化会使程序更加简洁。

  1. (直接使用具体建造者)由于是Builder模式来创建某个对象,因此就没有必要再定义一个Builder接口,直接提供一个具体的建造者类就可以了。

  2. (导演者的功能交由客户端完成)对于创建一个复杂,可能会有很多种不同的选择和步骤,我们也可以去掉Director类,将导演功能和Client的功能进行合并,也就是说,Client此时就是Director,它用来指导构建器类去构建需要的复杂对象。

场景2:

考虑这样一个实际应用,要创建一个保险合同的对象,里面很多属性的值都有约束,要求创建出来的对象是满足这些约束规则的。约束规则比如:保险合同通常情况下可以和个人签订,也可以和某个公司签订,但是一份保险合同不能同时与个人和公司签订。

  • Client可以使用DirectorBuilder完成对象的创建工作,但是实际上我们每创建一个新的产品,均要创造一个ConcreteBuilder具体创建者类,实现特有的零件。对于实时的用户自定义功能,我们可以在Directorconstruct方法设置参数,以达到用户实时传入的目的。正如2. 建造模式代码分析那样。

  • 用户在建造复杂对象时,或者对象的创建有顺序要求。那么我们可以将组装过程封装起来。组装就是创建对象,构造方法也是创建对象。故,可以将Director的职责交托给私有的构造方法

  • 我们也没必要使用抽象建造者接口,直接使用具体建造者类,在建造者类中可以返回构建完毕的产品对象

  • Direcor实时传入参数的问题,我们就交由具体构造者类去提供方法实现,Cilent负责传入。


温故知新:系统之间那点事-java内部类(我们为什么要设计内部类!)

  1. 静态内部类是一个独立的类,但是为什么使用静态内部类呢?(不要告诉我,你在开发中没有使用过内部类...)
    这时候,我们就可以具体分析了,此时有产品类ProductBuilder类,Builder虽然可以单独存在吧,但是只能创建Product产品,只能被对应的Product使用。那么此时应该怎么办?把Builder并入到Product中,搞的Product违反单一职责,如果把Builder独立,那么又可以被其他类使用,,不符合我们设计的本意。那么可以将其变成Product.Builder,其他类就不能使用Builder了。

好了,那就静态内部类了!


源码如下:

//保险者类
public class InsuranceContract {

    //保险合同编号
    private String contractId;
    //个人
    private String personName;
    //公司
    private String companyName;
    //保险生效日期
    private long begintDate;
    //保险失效日期
    private long endDate;
    //扩展数据
    private String otherData;
    

    //私有构造方法(导演者角色)
    private InsuranceContract(ContractBuilder builder) {
        this.contractId = builder.contractId;
        this.personName = builder.personName;
        this.companyName = builder.companyName;
        this.begintDate = builder.beginDate;
        this.endDate = builder.endDate;
        this.otherData = builder.otherData;
    }

    //具体的建造者合并到了产品对象中了,这个静态内部类之为外部类提供服务
    public static class ContractBuilder {
        private String contractId;
        private String personName;
        private String companyName;
        private long beginDate;
        private long endDate;
        private String otherData;

        public ContractBuilder(String contractId, long beginDate, long endDate) {
            this.contractId = contractId;
            this.beginDate = beginDate;
            this.endDate = endDate;
        }


        public ContractBuilder setPersonName(String personName) {
            this.personName = personName;
            return this;
        }

       //提供实时传入的接口
        public ContractBuilder setCompanyName(String companyName) {
            this.companyName = companyName;
            return this;
        }

        public ContractBuilder setOtherData(String otherData) {
            this.otherData = otherData;
            return this;
        }

        //建造方法,构造对象并返回(在建造方法中进行数据校验)
        public InsuranceContract build() {
            if (contractId == null || contractId.trim().length() == 0) {
                throw new IllegalArgumentException("contractId is null");
            }
            //判断个人或者公司是否为null
            boolean signPerson = personName != null && personName.trim().length() > 0;
            boolean signCompany = companyName != null && companyName.trim().length() > 0;
            if (signPerson && personName == null) {
                throw new IllegalArgumentException("companyName and personName 不能同时存在");
            }
            if (beginDate <= 0) {
                throw new IllegalArgumentException("beginDate 不合法");
            }
            if (endDate <= 0) {
                throw new IllegalArgumentException("endDate 不合法");
            }
            if (beginDate > endDate) {
                throw new IllegalArgumentException("beginDate大于endDate");
            }
            return new InsuranceContract(this);
        }
    }
}

测试类:

//客户端生成对象
public class BuilderClient {

    public static void main(String[] args) {
        //创建构造器对象
        InsuranceContract.ContractBuilder builder =
                new InsuranceContract.ContractBuilder("XP20180324", 20180322, 20190330);
        //设置需要的数据
        InsuranceContract contract = builder.setPersonName("小胖").setOtherData("扩展数据").build();
        System.out.println(contract);
    }
}

4. RabbitMQ-建造模式-源码分析

我们在设置BasicProperties()时候,是不是要写这么复杂的引用链代码,是不是有时候感觉到一脸蒙蔽?

public class MQBuilder {
    public static void main(String[] args) {
        // 一个是replyTo设置回调队列,另一是correlationId(相关性Id)为每个队列设置唯一值
        AMQP.BasicProperties props = new AMQP.BasicProperties().builder().correlationId("XP123")
                .replyTo("replyQueueName").build();
    }
}

那好我们就可以分析一下源码了。
正如温故知新——内部类中,我们知道静态内部类对象的创建方式:
外部类.内部类 XX=new 外部类.内部类();

  1. 那么我们分析其实BasicProperties就是一个静态内部类,最后的build方法【返回的Product产品】也就是new AMQP.basicProperties(XXX),完成具体建造类的功能之一:返回建造完毕产品对象。
    public AMQP.BasicProperties build() {
                return new AMQP.BasicProperties(this.contentType, 
this.contentEncoding, this.headers, this.deliveryMode, this.priority, 
this.correlationId, this.replyTo, this.expiration, this.messageId, 
this.timestamp, this.type, this.userId, this.appId, this.clusterId);
            }
  1. 我们继续看new AMQP.BasicProperties().builder()这个调用链,其实就是创建了内部静态内build对象。

Builder里面包含了零件信息,用户可以将自己需求传入到Builder对象,实现实时传入到Director的效果。

public static final class Builder {
            private String contentType;
            private String contentEncoding;
            private Map headers;
            private Integer deliveryMode;
            private Integer priority;
            private String correlationId;
            private String replyTo;
            private String expiration;
            private String messageId;
            private Date timestamp;
            private String type;
            private String userId;
            private String appId;
            private String clusterId;

            public Builder() {
            }

            public AMQP.BasicProperties.Builder contentType(String contentType) {
                this.contentType = contentType;
                return this;
            }

            public AMQP.BasicProperties.Builder contentEncoding(String contentEncoding) {
                this.contentEncoding = contentEncoding;
                return this;
            }

            public AMQP.BasicProperties.Builder headers(Map headers) {
                this.headers = headers;
                return this;
            }

            public AMQP.BasicProperties.Builder deliveryMode(Integer deliveryMode) {
                this.deliveryMode = deliveryMode;
                return this;
            }

            public AMQP.BasicProperties.Builder priority(Integer priority) {
                this.priority = priority;
                return this;
            }

            public AMQP.BasicProperties.Builder correlationId(String correlationId) {
                this.correlationId = correlationId;
                return this;
            }

            public AMQP.BasicProperties.Builder replyTo(String replyTo) {
                this.replyTo = replyTo;
                return this;
            }

            public AMQP.BasicProperties.Builder expiration(String expiration) {
                this.expiration = expiration;
                return this;
            }

            public AMQP.BasicProperties.Builder messageId(String messageId) {
                this.messageId = messageId;
                return this;
            }

            public AMQP.BasicProperties.Builder timestamp(Date timestamp) {
                this.timestamp = timestamp;
                return this;
            }

            public AMQP.BasicProperties.Builder type(String type) {
                this.type = type;
                return this;
            }

            public AMQP.BasicProperties.Builder userId(String userId) {
                this.userId = userId;
                return this;
            }

            public AMQP.BasicProperties.Builder appId(String appId) {
                this.appId = appId;
                return this;
            }

            public AMQP.BasicProperties.Builder clusterId(String clusterId) {
                this.clusterId = clusterId;
                return this;
            }

            public AMQP.BasicProperties build() {
                return new AMQP.BasicProperties(this.contentType, this.contentEncoding, this.headers, this.deliveryMode, this.priority, this.correlationId, this.replyTo, this.expiration, this.messageId, this.timestamp, this.type, this.userId, this.appId, this.clusterId);
            }
        }
    }

5. lombok的@Builder注解

在lombok插件中使用@Builder注解,就可以为POJO提供建造者模式:

源代码:

@Builder(toBuilder = true)
@Getter
public class Address {
    private String address;
    private String phone;
}

class反编译文件

package com.protoType.MySchema;

public class Address
{
  private String address;
  private String phone;

  Address(String address, String phone)
  {
    this.address = address; this.phone = phone; } 
  public static AddressBuilder builder() { return new AddressBuilder(); }

  public AddressBuilder toBuilder() { return new AddressBuilder().address(this.address).phone(this.phone); }

  public String getAddress() { return this.address; } 
  public String getPhone() { return this.phone;
  }

  public static class AddressBuilder
  {
    private String address;
    private String phone;

    public AddressBuilder address(String address)
    {
      this.address = address; return this; } 
    public AddressBuilder phone(String phone) { this.phone = phone; return this; } 
    public Address build() { return new Address(this.address, this.phone); } 
    public String toString() { return "Address.AddressBuilder(address=" + this.address + ", phone=" + this.phone + ")";
    }
  }
}

我看可以看到,这是一个典型的建造者模式,使用静态内部类充当建造者角色,建造者角色包含产品的所有零件,用户组装零件,将信息保存到具体建造者中,组装完成,开始生成产品角色,即调用产品的构造方法。【注意:产品的构造方法为default访问权限】

6. 总结

建造者模式将对象的生成分成了两步。(1)建造轮子;(2)将轮子安装到车子上。但是也不要拘泥于固定的模板,对于一些复杂对象的创建过程,我们可以考虑使用建造者模式创建。创建的时候可以使用静态内部类创建。

你可能感兴趣的:(设计模式那点事-创建型模式-建造模式(PK-工厂模式)(温故知新-内部类)(源码分析-RabbitMQ的建造模式的使用)(lombok的@Builder注解))