1.引子

关于创建型设计模式,我们已经分享了单例设计模式,和工厂设计模式。创建型设计模式其实不难理解,我们抓住本质,所谓创建型设计模式都与对象的创建相关,并且在代码实现层面区分度都比较高,你应该都还记得如何实现单例设计模式(懒汉式、饿汉式),如何实现工厂模式(简单工厂、工厂方法、抽象工厂)对吧

那么今天,我将继续给你分享另外一个在实际开发中,使用比较多的创建型设计模式,它就是建造者设计模式。并且我将重点给你分享为什么需要建造者设计模式?以及建造者设计模式解决了什么问题?我想这个对于我们掌握设计模式会更加重要一些,知道做什么为什么。至于代码层面,有时候还次要一些。

让我们开始吧。

2.案例

2.1.给一个需要建造者设计模式的理由

暂时我们抛开设计模式,回归本质。请你轻轻的闭上眼睛,慢慢地深呼吸,是不是感觉到一阵早春的气息扑面而来!

停!请你不要瞎想了。我们是程序员,不是诗人。你应该听见的是键盘声、电流声声声入耳,绵绵不绝,而不是流水叮咚声!

回来吧,想一想对象是如何创建的?

日常开发中,当我们需要一个对象的时候,我们通常会通过构造方法创建对象,或者再进一步通过set方法初始化成员变量,像下面这样

用户实体类

public class User{
    private String name;
    private Integer sex;
    private Integer age;

    public User(){}

    public User(String name, Integer sex, Integer age){
        this.name = name;
        this.sex = sex;
        this.age = age;
    }

    /***************省略get方法****************/
    public void setName(String name){
        this.name = name;
    }

    public void setSex(Integer sex){
        this.sex = sex;
    }

    public setAge(Integer age){
        this.age = age;
    }
}

创建用户对象

// 方式一:直接通过构造方法
User user = new User("小明",1,18);

// 方式二:通过无参构造方法 + set方法
User user = new User();
user.setName("小明");
user.setSex(1);
user.setAge(18);

以上创建用户User对象的代码,我们非常熟悉。那么你是否曾想过,这样创建使用对象的方式有什么问题吗?你可以先简单的思考一下,再往下看我提出的问题。

  • 思考一:如果我们要创建的对象,成员变量非常多。比如说有20个、30个、甚至更多。你会怎么做?通过带参数构造方法,构造方法会因为参数太多,从而导致代码易用性、可读性差。试想调用一个带30个参数的构造方法,心理阴影面积有多大,像这样

User user = new User(参数1,参数2,参数3,......参数30);
  • 思考二:你说User类的成员变量虽然多,但是我们在实际使用的时候,并不是所有成员变量都要赋值。这样一来就好办了,通过无参数构造方法+set方法,像这样
User user = new User();
user.set参数1(参数1);
user.set参数2(参数2);
......给需要的成员变量,通过set方法赋值......
......用不到的成员变量,就不用管了,问题解决......
  • 思考三:通过无参数构造方法+set方法,从某种角度上解决了我们的问题。但是,如果成员变量需要有校验逻辑(比如非空,比如取值范围等);再比如成员变量之间有依赖关系校验逻辑(性别男,退休年龄60岁;性别女,退休年龄55岁)。这样的校验逻辑,该放到什么地方处理呢?构造方法中?set方法中?似乎都不合适!我们需要一个统一的地方,来统一处理校验逻辑。这个时候我们本文的主角建造者设计模式,终于举手站了起来说:是了,这些事情我来处理最合适了

通过日常开发中,我们都非常熟悉的一个案例,以及思考一、思考二、思考三几个问题。我们引出了建造者设计模式的适用场景,来简单总结一下

  • 如果要创建的目标对象,成员变量比较多,且对象的使用比较灵活(每次创建对象,不需要全部的成员变量,而是部分成员变量的组合)

  • 如果要创建的目标对象,成员变量需要进行一定的业务校验,甚至成员变量之间有业务依赖关系校验

基于以上两点,我们适合使用建造者设计模式,也是我想分享给你:需要建造者设计模式的理由。接下来我们通过代码示例来演示建造者设计模式的实现。

2.2.代码示例

2.2.1.用户实体User类

/**
 * 用户实体类
 *
 * @author ThinkPad
 * @version 1.0
 * @date 2021/3/5 20:11
 */
public class User {

    /**
     * 姓名
     * */
    private String name;
    /**
     * 性别:1 男  ; 2 女
     * */
    private Integer sex;
    /**
     * 退休年龄:男 60 岁;女 55岁
     * */
    private Integer retirementAge;

    /*****************get方法***************************/
    public String getName() {
        return name;
    }

    public Integer getSex() {
        return sex;
    }

    public Integer getRetirementAge() {
        return retirementAge;
    }

    /*****************set方法***************************/
    public void setName(String name) {
        this.name = name;
    }

    public void setSex(Integer sex) {
        this.sex = sex;
    }

    public void setRetirementAge(Integer retirementAge) {
        this.retirementAge = retirementAge;
    }

    /**
     * 用户建造者设计类
     */
    public static class UserBuilder{
        /**拷贝用户实体类成员变量*/
        private String name;
        private Integer sex;
        private Integer retirementAge;

        /**无参数构造方法*/
        UserBuilder(){}

        /**相当于set方法,注意返回builder对象*/
       public User.UserBuilder name(String name){
            this.name = name;
            return this;
       }

        public User.UserBuilder sex(Integer sex){
            this.sex = sex;
            return this;
        }

        public User.UserBuilder retirementAge(Integer retirementAge){
            this.retirementAge = retirementAge;
            return this;
        }

        /**
         * 建造者设计模式代码实现,核心方法
         * 此方法中,校验姓名不能为空
         * 此方法中,模拟校验性别与退休年龄的依赖关系,即:
         * 1.性别男,退休年龄60岁
         * 2.性别女,退休年龄55岁
         * */
        public User build(){
            // 校验姓名不能为空
           if(this.name == null || "".equals(this.name)){
               throw new RuntimeException("用户姓名不能为空!");
           }

           // 校验性别与退休年龄依赖关系
            if(this.sex == 1 && this.retirementAge < 60){
               throw new RuntimeException("男人命苦,退休年龄必须是60岁以后!");
            }else if(this.sex == 2 && this.retirementAge < 55){
                throw new RuntimeException("女人也不容易,退休年龄必须是55岁以后!");
            }

            // 创建用户对象
            User user = new User();
            user.setName(this.name);
            user.setSex(this.sex);
            user.setRetirementAge(this.retirementAge);

           return user;
        }

    }

}

2.2.2.用户建造者UserBuilder类

建造者设计模式的应用,很多时候我们会直接把建造者类,在它所对应的目标类中实现,为的是增强代码的内聚性。比如UserBuilder,在User类的内部。

这里我单独把UserBuilder类的代码贴出来,是方便你更清晰的看到建造者设计模式实现的关键地方,主要有三个

  • 建造者类的成员变量,与目标类的成员变量一致

  • 建造者类,需要给每个成员变量提供对应的,相当于set赋值方法

  • 建造者类,重点是提供build方法,该方法实现业务逻辑的统一处理,以及构建目标类对象并返回

/**
* 用户建造者设计类
*/
public static class UserBuilder{
 /**拷贝用户实体类成员变量*/
 private String name;
 private Integer sex;
 private Integer retirementAge;

 /**无参数构造方法*/
 UserBuilder(){}

 /**相当于set方法,注意返回builder对象*/
 public User.UserBuilder name(String name){
     this.name = name;
     return this;
 }

 public User.UserBuilder sex(Integer sex){
   this.sex = sex;
   return this;
 }

 public User.UserBuilder retirementAge(Integer retirementAge){
   this.retirementAge = retirementAge;
   return this;
 }

 /**
 * 建造者设计模式代码实现,核心方法
 * 此方法中,校验姓名不能为空
 * 此方法中,模拟校验性别与退休年龄的依赖关系,即:
 * 1.性别男,退休年龄60岁
 * 2.性别女,退休年龄55岁
 * */
 public User build(){
  // 校验姓名不能为空
  if(this.name == null || "".equals(this.name)){
    throw new RuntimeException("用户姓名不能为空!");
   }

   // 校验性别与退休年龄依赖关系
   if(this.sex == 1 && this.retirementAge < 60){
      throw new RuntimeException("男人命苦,退休年龄必须是60岁以后!");
   }else if(this.sex == 2 && this.retirementAge < 55){
     throw new RuntimeException("女人也不容易,退休年龄必须是55岁以后!");
   }

   // 创建用户对象
   User user = new User();
   user.setName(this.name);
   user.setSex(this.sex);
   user.setRetirementAge(this.retirementAge);

   return user;
 }

}

2.2.3.使用建造者设计模式

2.2.3.1.正常执行案例

public static void main(String[] args) {
    // 创建建造者对象
   User.UserBuilder builder = new User.UserBuilder();
   // 通过建造者,创建用户对象
   User user = builder.name("小明")
           .sex(1)
           .retirementAge(60)
           .build();

   // 输出用户对象
  System.out.println("用户名称:" + user.getName()
        + ",性别:" + user.getSex()
        + ",退休年龄:" + user.getRetirementAge());
}

#执行结果==================================
用户名称:小明,性别:1,退休年龄:60

Process finished with exit code 0

2.2.3.2.测试校验异常案例

public static void main(String[] args) {
    // 创建建造者对象
   User.UserBuilder builder = new User.UserBuilder();
   // 通过建造者,创建用户对象
   User user = builder.name("小明")
           .sex(1)
           .retirementAge(59)// 设置退休年龄 59岁,不满足男人  60岁退休的约束
           .build();

   // 输出用户对象
  System.out.println("用户名称:" + user.getName()
        + ",性别:" + user.getSex()
        + ",退休年龄:" + user.getRetirementAge());
}

#执行结果==================================
Exception in thread "main" java.lang.RuntimeException: 男人命苦,退休年龄必须是60岁以后!
	at com.anan.edu.common.design.pattern.create.builder.User$UserBuilder.build(User.java:94)
	at com.anan.edu.common.design.pattern.create.builder.BuilderDemo.main(BuilderDemo.java:19)

Process finished with exit code 1
03-07 14:34