0
点赞
收藏
分享

微信扫一扫

【设计模式】建造者模式 Builder:创建不同形式复杂对象

(目录)

创建型设计模式——Builder 模式,中文一般叫建造者模式生成器模式。 建造者模式的代码实现非常简单,原理掌握起来也不难,而难点就在于什么时候采用它。比如,经常会遇到的以下两个问题:

  • 为什么直接使用构造函数或者使用 set 方法来创建对象不方便?
  • 为什么一定需要建造者模式来创建?

建造者模式

建造者模式的定义是这样的:

将复杂对象的构造与其表示分离,以便同一构造过程可以创建不同的表示。

不⽤建造者有什么麻烦?

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


/**
 * 基于构造⽅法为属性赋值⽆法适⽤于灵活多变的环境,且参数太⻓很难使⽤
 */
public class RabbitMQClientSample1 {
    private String host = "127.0.0.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("路由模式⽆须设置队列名称");
            }
        }
        //其他各种验证
    }

    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");
    }
}


我的天,这么多参数要死啊,还是改⽤set⽅法灵活赋值吧

public class RabbitMQClientSample2 {
    private String host = "127.0.0.1";
    private int port = 5672;
    private int mode;
    private String exchange;
    private String queue;
    private boolean isDurable = true;
    int connectionTimeout = 20;

    //让对象不可变
    private RabbitMQClientSample2() {
    }

    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.setQueue("queue");
        client.validate();
        client.sendMessage("Test");
    }
}

利⽤SET⽅法虽然灵活,但是存在中间状态,且属性校验时有前后顺序 约束,或者还需要构建额外的校验⽅法 并且SET⽅法破坏了“不可变对象”的密闭性 如何保障灵活组织参数,有可以保证不会存在中间状态以及基本信息不 会对外泄露呢?

建造者模式是⼀个好选择

构建者模式的特点:摆脱超⻓构造⽅法参数的束缚的同时也保护了”不 可变对象“的密闭性

建造者模式的格式如下: ⽬标类的构造⽅法要求传⼊Builder对象 Builder建造者类位于⽬标类内部且⽤static描述 Builder建造者对象提供内置属性与各种set⽅法,注意set⽅法返回 Builder对象本身 Builder建造者类提供build()⽅法实现⽬标类对象的创建

Builder

public class ⽬标类(){
 //⽬标类的构造⽅法要求传⼊Builder对象
 public ⽬标类(Builder builder){

 }

 public 返回值 业务⽅法(参数列表){
 //doSth
 }
 //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);
 }
 }
}

/**
 * 建造者模式进⾏优化
 */
public class RabbitMQClient {
    private RabbitMQClient(Builder builder) {
    }

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

    public static class Builder {
        private String host = "127.0.0.1";
        private int port = 5672;
        private int mode;
        private String exchange;
        private String queue;
        private boolean isDurable = true;
        private int connectionTimeout = 20;

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

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

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

        public Builder setExchange(String exchange) {
            this.exchange = exchange;
            return this;
        }

        public Builder setQueue(String queue) {
            this.queue = queue;
            return this;
        }

        public Builder setDurable(boolean durable) {
            isDurable = durable;
            return this;
        }

        public RabbitMQClient build() {
            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 new RabbitMQClient(this);
        }
    }

    public static void main(String[] args) {
        RabbitMQClient client = new RabbitMQClient.Builder()
                .setHost("192.168.31.201").setMode(2).setExchange("test-ex
                        change").build();
                        client.sendMessage("Test");
    }
}

Lombok @Builder 注解


@Builder
public class lombook {

    private String host = "127.0.0.1";
    private int port = 5672;
    private int mode;
    private String exchange;
    private String queue;
    private boolean isDurable = true;
    private int connectionTimeout = 20;

    public void sendMessage(String message) {
        System.out.println("sendMessage: " + message);
    }
}

测试:

public static void main(String[] args) {
    lombook build = lombook.builder().host("host").port(8080).mode(11).build();

    build.sendMessage("hello");
}

展示: image.png

优缺点

优点有以下三点:

  1. 分离创建与使用。 在建造者模式中, 使用方不必知道你的内部实现算法(步骤)的细节,通过统一方法接口的调用,可以自由组合出不同的对象实例;
  2. 满足开闭原则。 每一个建造者都相对独立,因此能方便地进行替换或新增,这就大大提升了代码的可扩展性;
  3. 自由地组合对象的创建过程。由于建造者模式将复杂的创建步骤拆分为单个独立的创建步骤,这不仅使得代码的可读性更高,也使得在创建过程中,使用者可以根据自己的需要灵活创建。

缺点也有以下三点:

  1. 使用范围有限。 建造者模式所创建的对象一般都需要有很多的共同点,如果对象实例之间的差异性很大,则不适合使用建造者模式;
  2. 容易引起超大的类。我们都知道一辆汽车内部构造其实很复杂,作为开发者的你其实更关心的是像发动机、轮胎这样具备重用性的组件。一旦过度定制化对象创建的过程步骤,那么随着创建对象新需求的出现或变化,新的创建步骤就会被加进来,这会造成代码量的急剧膨胀,最终形成一个庞大的超大类;
  3. 增加代码行数。 虽然建造者模式能够提高代码的可阅读性,但也会以增加代码行数来作为代价。

总结

在现实中,经常会遇见很多使用建造者模式的软件,比如:

  • Maven、Ant 之类的自动化构建系统;
  • Jenkins 等持续集成系统

它们实际上都是使用了建造者模式的具体例子。

建造者模式的主要优点:在于客户端不必知道对象内部组成的细节,将创建与使用进行了很好的解耦,使得可以使用相同的创建过程创建不同的对象,因此符合“开闭原则”,能够极大地提升代码的复用性。同时,因为每一个对象属性的创建步骤都被独立出来,所以还可以更加精细地控制对象的创建过程。 不过,缺点也同样突出。当使用建造者模式创建对象时,需要对象具备更多的共同点才能抽象出更适合的步骤,因此使用范围会受到很大的限制,一旦产品内部开始变得复杂,可能就会导致需要定义很多定制化的建造者类来适应这种变化,从而导致代码变得非常庞大。 应用建造者模式的关键就在于抓住拆分步骤,这是与工厂模式最大的区别所在。如果把建造者模式比作汽车整体组装工厂,那么工厂模式就是汽车配件组装工厂,前者侧重于把对象按照特定步骤组装完整,后者侧重于把组成对象的每一个属性或方法做得更通用后再组装。一定不要以为只要叫“工厂”就是指我们通常认为的统一组装模式。

举报

相关推荐

0 条评论