建造者模式

来自姬鸿昌的知识库
Jihongchang讨论 | 贡献2022年8月28日 (日) 22:16的版本 →‎建造者模式是一个好选择
(差异) ←上一版本 | 最后版本 (差异) | 下一版本→ (差异)
跳到导航 跳到搜索

https://www.bilibili.com/video/BV1rV4y1s7JG

1.不用建造者有什么麻烦?

假设我们要自己开发一个RabbitMQ消息队列的客户端,有很多需要初始化的参数,你会怎么做?

package io.github.jihch;

public class RabbitMQClientSample1 {

    private String host = "127.9.9.1";

    private int port = 5672;

    private int mode;

    private String exchange;

    private String queue;

    private boolean isDurable = true;

    int connectionTimeout = 1000;

    private RabbitMQClientSample1(String host, int port, int mode, String exchange, String queue, boolean isDurable,
                                  int connectionTimeout) {
        this.host = host;
        this.port = port;
        this.mode = mode;
        this.exchange = exchange;
        this.queue = queue;
        this.isDurable = isDurable;
        this.connectionTimeout = connectionTimeout;
        if (mode == 1) {//工作队列模式不需要设置交换机,但queue必填
            if (exchange != null) {
                throw new RuntimeException("工作队列模式无须设计交换机");
            }
            if (queue == null || queue.trim().equals("")) {
                throw new RuntimeException("工作队列模式必须设置队列名称");
            }
            if (isDurable == false) {
                throw new RuntimeException("工作队列模式必须开启数据持久化");
            }
        } else if (mode == 2) { //路由模式必须设置交换机,但不能设置 queue 队列
            if (exchange == null || exchange.trim().equals("")) {
                throw new RuntimeException("路由模式请设置交换机");
            }
            if (queue != null) {
                throw new RuntimeException("路由模式无需设置队列名称");
            }
        }
        //其他各种验证
    }// end constructor


    public void sendMessage(String msg) {
        System.out.println("正在发送消息:" + msg);
    }

    public static void main(String[] args) {
        RabbitMQClientSample1 client = new RabbitMQClientSample1("192.168.31.210", 5672, 2, "sample-exchange", null,
                true, 5000);
        client.sendMessage("Test");
    }
}

每次使用构造方法创建新的对象都要传入很多参数(想想生产环境如果按数据表抽象,一张表有多少字段?!就算默认值可以为 null,一个个字段对照填写费劲不费劲?!),其中有一些参数在一些情况下使用默认值就可以,并不是必要填写的,还是改用 set 方法灵活赋值吧

package io.github.jihch;

public class RabbitMQClientSample2 {

    private String host = "127.9.9.1";

    private int port = 5672;

    private int mode;

    private String exchange;

    private String queue;

    private boolean isDurable = true;

    int connectionTimeout = 1000;

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public int getMode() {
        return mode;
    }

    public void setMode(int mode) {
        this.mode = mode;
    }

    public String getExchange() {
        return exchange;
    }

    public void setExchange(String exchange) {
        if (mode == 1) {//工作队列模式不需要设置交换机,但queue必填
            if (exchange != null) {
                throw new RuntimeException("工作队列模式无须设计交换机");
            }
            if (queue == null || queue.trim().equals("")) {
                throw new RuntimeException("工作队列模式必须设置队列名称");
            }
            if (isDurable == false) {
                throw new RuntimeException("工作队列模式必须开启数据持久化");
            }
        } else if (mode == 2) { //路由模式必须设置交换机,但不能设置 queue 队列
            if (exchange == null || exchange.trim().equals("")) {
                throw new RuntimeException("路由模式请设置交换机");
            }
            if (queue != null) {
                throw new RuntimeException("路由模式无需设置队列名称");
            }
        }
        this.exchange = exchange;
    }

    public String getQueue() {
        return queue;
    }

    public void setQueue(String queue) {
        if (mode == 1) {//工作队列模式不需要设置交换机,但queue必填
            if (exchange != null) {
                throw new RuntimeException("工作队列模式无须设计交换机");
            }
            if (queue == null || queue.trim().equals("")) {
                throw new RuntimeException("工作队列模式必须设置队列名称");
            }
            if (isDurable == false) {
                throw new RuntimeException("工作队列模式必须开启数据持久化");
            }
        } else if (mode == 2) { //路由模式必须设置交换机,但不能设置 queue 队列
            if (exchange == null || exchange.trim().equals("")) {
                throw new RuntimeException("路由模式请设置交换机");
            }
            if (queue != null) {
                throw new RuntimeException("路由模式无需设置队列名称");
            }
        }
        this.queue = queue;
    }

    public boolean isDurable() {
        return isDurable;
    }

    public void setDurable(boolean durable) {
        isDurable = durable;
    }

    public int getConnectionTimeout() {
        return connectionTimeout;
    }

    public void setConnectionTimeout(int connectionTimeout) {
        this.connectionTimeout = connectionTimeout;
    }

    //没办法,必须增加一个额外的 validate 方法验证对象是否符合要求
    public boolean validate() {
        if (mode == 1) {//工作队列模式不需要设置交换机,但queue必填
            if (exchange != null) {
                throw new RuntimeException("工作队列模式无须设计交换机");
            }
            if (queue == null || queue.trim().equals("")) {
                throw new RuntimeException("工作队列模式必须设置队列名称");
            }
            if (isDurable == false) {
                throw new RuntimeException("工作队列模式必须开启数据持久化");
            }
        } else if (mode == 2) { //路由模式必须设置交换机,但不能设置 queue 队列
            if (exchange == null || exchange.trim().equals("")) {
                throw new RuntimeException("路由模式请设置交换机");
            }
            if (queue != null) {
                throw new RuntimeException("路由模式无需设置队列名称");
            }
        }
        return true;
    }

    public void sendMessage(String msg) {
        System.out.println("正在发送消息:" + msg);
    }

    public static void main(String[] args) {
        RabbitMQClientSample2 client = new RabbitMQClientSample2();
        client.setHost("192.168.31.210");
        client.setMode(1);
        client.setDurable(true);
        client.validate();
        client.sendMessage("Test");
    }
}

利用 set 方法虽然灵活,但是存在中间状态,且属性校验时有前后顺序约束,或者还需要构建额外的校验方法

并且 set 方法破坏了“不可变对象”的密闭性

怎么才能既可以灵活组织参数,又保证不会存在中间状态,还能保证基本信息不会对外泄漏呢?

建造者模式是一个好选择

建造者模式的格式如下:

  • 目标类的构造方法要求传入 Builder 对象
  • Builder 建造者类位于目标类内部且用 static 修饰
  • Builder 建造者对象提供内置属性与各种 set 方法,注意 set 方法返回 Builder 对象本身
  • Builder 建造者提供 build() 方法实现目标类对象的创建

Builder

package io.github.jihch;

public class 目标类 {

    //目标类的构造方法要求传入 Builder 对象
    private 目标类(Builder builder) {

    }

    public 返回值 业务方法(参数列表) {

    }


    //Builder 建造者类位于目标类内部且用 static 描述
    public static class Builder {

        //Builder 建造者对象提供内置属性与各种 set 方法,注意 set 方法返回 Builder 对象本身
        private String xxx;

        public Builder setXxx(String xxx) {
            this.xxx = xxx;
            return this;
        }

        //Builder 建造者类提供 build() 方法实现目标类对象的创建
        public 目标类 build() {
            //业务校验
            return new 目标类(this);
        }


    }

}

https://github.com/jihch/public/tree/main/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/%E5%BB%BA%E9%80%A0%E8%80%85%E6%A8%A1%E5%BC%8F/constructor