第四章 4.4 建造者模式

分类:01_设计模式

标签:

4.4.1 建造者模式介绍

建造者模式 (builder pattern), 也被称为生成器模式 , 是一种创建型设计模式.

建造者模式要解决的问题

建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节。

比如: 一辆汽车是由多个部件组成的,包括了车轮、方向盘、发动机等等.对于大多数用户而言,并不需要知道这些部件的装配细节,并且几乎不会使用单独某个部件,而是使用一辆完整的汽车.而建造者模式就是负责将这些部件进行组装让后将完整的汽车返回给用户.

50.jpg

4.4.2 建造者模式原理

建造者(Builder)模式包含以下4个角色 :

52.jpg

4.4.3 建造者模式实现方式1

创建共享单车

生产自行车是一个复杂的过程,它包含了车架,车座等组件的生产。而车架又有碳纤维,铝合金等材质的,车座有橡胶,真皮等材质。对于自行车的生产就可以使用建造者模式。

这里Bike是产品,包含车架,车座等组件;Builder是抽象建造者,MobikeBuilder和HelloBuilder是具体的建造者;Director是指挥者。类图如下:

53.jpg

具体产品


public class Bike {

   //车架
   private String frame;

   //座椅
   private String seat;

   public String getFrame() {
       return frame;
   }

   public void setFrame(String frame) {
       this.frame = frame;
   }

   public String getSeat() {
       return seat;
   }

   public void setSeat(String seat) {
       this.seat = seat;
   }
}

构建者类


public abstract class Builder {

   protected Bike mBike = new Bike();

   public abstract void buildFrame();
   public abstract void buildSeat();
   public abstract Bike createBike();
}

public class HelloBuilder extends Builder {
   @Override
   public void buildFrame() {
       mBike.setFrame("碳纤维车架");
   }

   @Override
   public void buildSeat() {
       mBike.setSeat("橡胶车座");
   }

   @Override
   public Bike createBike() {
       return mBike;
   }
}

public class MobikeBuilder extends Builder {

   @Override
   public void buildFrame() {
       mBike.setFrame("铝合金车架");
   }

   @Override
   public void buildSeat() {
       mBike.setSeat("真皮车座");
   }

   @Override
   public Bike createBike() {
       return mBike;
   }
}

指挥者类


public class Director {

   private Builder mBuilder;

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

   public Bike construct() {
       mBuilder.buildFrame();
       mBuilder.buildSeat();
       return mBuilder.createBike();
   }
}

客户端


public class Client {

   public static void main(String[] args) {
       showBike(new HelloBuilder());
       showBike(new MobikeBuilder());
   }

   private static void showBike(Builder builder) {
       Director director = new Director(builder);
       Bike bike = director.construct();
       System.out.println(bike.getFrame());
       System.out.println(bike.getSeat());
   }
}

4.4.4 建造者模式实现方式2

建造者模式除了上面的用途外,在开发中还有一个常用的使用方式,就是当一个类构造器需要传入很多参数时,如果创建这个类的实例,代码可读性会非常差,而且很容易引入错误,此时就可以利用建造者模式进行重构。

1 ) 构造方法创建复杂对象的问题


package com.mashibing.example02;

/**
* MQ连接客户端
* @author spikeCong
* @date 2022/9/19
**/
public class RabbitMQClient1 {

   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;

   
   //构造方法参数过多,代码的可读性和易用性太差,在使用构造函数时,很容易搞错顺序,传递错误的参数值,导致很有隐蔽的BUG
   public RabbitMQClient1(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){ //工作队列模式不需要设计交换机,但是队列名称一定要有
           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){ //路由模式必须设计交换机,但是不能设计队列
           if(exchange == null){
               throw new RuntimeException("路由模式下必须设置交换机");
           }
           if(queue != null){
               throw new RuntimeException("路由模式无须设计队列名称");
           }
       }

       //其他验证方式,
   }

   public void sendMessage(String msg){

       System.out.println("发送消息......");
   }

   public static void main(String[] args) {
       //每一种模式,都需要根据不同的情况进行实例化,构造方法会变得过于复杂.
       RabbitMQClient1 client1 = new RabbitMQClient1("192.168.52.123",5672,
               2,"sample-exchange",null,true,5000);

       client1.sendMessage("Test-MSG");
   }
}

2) set方法创建复杂对象的问题


package com.mashibing.example02;

/**
* MQ连接客户端
* @author spikeCong
* @date 2022/9/19
**/
public class RabbitMQClient2 {

   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 RabbitMQClient2() {

   }

   public String getExchange() {
       return exchange;
   }

   public void setExchange(String exchange) {

       if(mode == 1){ //工作队列模式不需要设计交换机,但是队列名称一定要有
           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){ //路由模式必须设计交换机,但是不能设计队列
           if(exchange == null){
               throw new RuntimeException("路由模式下必须设置交换机");
           }
           if(queue != null){
               throw new RuntimeException("路由模式无须设计队列名称");
           }
       }

       //其他验证方式,

       this.exchange = exchange;
   }

   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) {

       if(mode == 1){ //工作队列模式不需要设计交换机,但是队列名称一定要有
           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){ //路由模式必须设计交换机,但是不能设计队列
           if(exchange == null){
               throw new RuntimeException("路由模式下必须设置交换机");
           }
           if(queue != null){
               throw new RuntimeException("路由模式无须设计队列名称");
           }
       }

       this.mode = mode;
   }

   public String getQueue() {
       return queue;
   }

   public void setQueue(String queue) {
       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;
   }

   public void sendMessage(String msg){

       System.out.println("发送消息......");
   }

   /**
    * set方法的好处是参数的设计更加的灵活,但是通过set方式设置对象属性时,对象有可能存在中间状态(无效状态),
    * 并且进行属性校验时有前后顺序约束.
    * 怎么保证灵活设置参数又不会存在中间状态呢? 答案就是: 使用建造者模式
    */
   public static void main(String[] args) {

       RabbitMQClient2 client2 = new RabbitMQClient2();
       client2.setHost("192.168.52.123");
       client2.setQueue("queue");
       client2.setMode(1);
       client2.setDurable(true);
       client2.sendMessage("Test-MSG2");
   }
}


3) 建造者方式实现

建造者使用步骤如下:

  1. 目标类的构造方法要传入Builder对象

  2. Builder建造者类位于目标类内部,并且使用static修饰

  3. Builder建造者对象提供内置的各种set方法,注意set方法返回的是builder对象本身

  4. Builder建造者类提供build()方法实现目标对象的创建


public class 目标类{

   //目标类的构造方法需要传入Builder对象
   public 目标类(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);
       }
   }
}

重写案例代码


/**
* 建造者模式
* @author spikeCong
* @date 2022/9/19
**/
public class RabbitMQClient {

   //私有构造方法
   private RabbitMQClient(Builder builder) {

   }

   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;
       int connectionTimeout = 1000;

       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 Builder setConnectionTimeout(int connectionTimeout) {
           this.connectionTimeout = connectionTimeout;
           return this;
       }


       //返回构建好的复杂对象
       public RabbitMQClient build(){
           //首先进行校验
           if(mode == 1){ //工作队列模式不需要设计交换机,但是队列名称一定要有
               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){ //路由模式必须设计交换机,但是不能设计队列
               if(exchange == null){
                   throw new RuntimeException("路由模式下必须设置交换机");
               }
               if(queue != null){
                   throw new RuntimeException("路由模式无须设计队列名称");
               }
           }

           return new RabbitMQClient(this);
       }
   }

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

测试


public class MainAPP {

   public static void main(String[] args) {

       //使用链式编程设置参数
       RabbitMQClient client = new RabbitMQClient.Builder().setHost("192.168.52.123").setMode(2).setExchange("text-exchange")
               .setPort(5672).setDurable(true).build();

       client.sendMessage("Test");
   }
}

4.4.5 建造者模式总结

1) 建造者模式与工厂模式区别

举例: 顾客走进一家餐馆点餐,我们利用工厂模式,根据用户不同的选择,来制作不同的食物,比如披萨、汉堡、沙拉。对于披萨来说,用户又有各种配料可以定制,比如奶酪、西红柿、起司,我们通过建造者模式根据用户选择的不同配料来制作披萨。


2) 建造者模式的优缺点

3) 应用场景


修改内容