大聪明教你学Java设计模式 | 第二篇:建造者模式

2,171 阅读9分钟

前言

大聪明在写代码的过程中发现设计模式的影子是无处不在,设计模式也是软件开发人员在软件开发过程中面临的一般问题的解决方案。大聪明本着“独乐乐不如众乐乐”的宗旨与大家分享一下设计模式的学习心得。 今天就与大家分享一下设计模式中的建造者模式。

1.建造者模式

首先还是先简单说一下建造者模式的定义:将一个复杂对象的构建与其表示分离,使得同样的构建过程可以创建不同的表示。 这定义说的就相当抽象了,大聪明看到定义后也是大喊道:我TM直呼好家伙! 大聪明为了各位小伙伴更好的理解和学习建造者模式,还是用最简单最形象的方式为大家讲解一下。

(1)经典建造者模式

经典的建造者模式中一共有四个角色,分别是:

  • 要建造的产品Product
  • 抽象的Builder
  • Builder的具体实现ConcreteBuilder
  • 使用者Director

这么说还是有点抽象,那咱们再说的更具体一点,就以组装电脑为例子吧~

①首先咱们先创建一个Computer类

public class Computer {
    //CPU
    private String CPU;
    //内存
    private String memory;
    //硬盘
    private String hardDisk;
    //键盘
    private String keyboard;
    //鼠标
    private String mouse;

    public String getCPU() {
        return CPU;
    }

    public void setCPU(String CPU) {
        this.CPU = CPU;
    }

    public String getMemory() {
        return memory;
    }

    public void setMemory(String memory) {
        this.memory = memory;
    }

    public String getHardDisk() {
        return hardDisk;
    }

    public void setHardDisk(String hardDisk) {
        this.hardDisk = hardDisk;
    }

    public String getKeyboard() {
        return keyboard;
    }

    public void setKeyboard(String keyboard) {
        this.keyboard = keyboard;
    }

    public String getMouse() {
        return mouse;
    }

    public void setMouse(String mouse) {
        this.mouse = mouse;
    }

    @Override
    public String toString() {
        return "Computer{" +
                "CPU='" + CPU + '\'' +
                ", memory='" + memory + '\'' +
                ", hardDisk='" + hardDisk + '\'' +
                ", keyboard='" + keyboard + '\'' +
                ", mouse='" + mouse + '\'' +
                '}';
    }
}

这个Computer很简单,一共包含了五个基本属性:CPU、内存、硬盘、键盘、鼠标。

②然后咱们再创建一个抽象的电脑组装过程的Builder类:

public interface ComputerConfigBuilder {
    void setCPU();
    void setMemery();
    void setHardDisk();
    void setKeyboard();
    void setMouse();
    Computer getComputer();
}

电脑组装一般都需要安装CPU、内存条、硬盘、键盘鼠标,我们把这一安装过程给抽象出来,至于具体安装什么就需要通过其实现类来实现,其中还定义了一个获取Computer的方法。

③接下来我们就创建具体的实现类。电脑一般都有低配版和高配版,选用不同配置,组装成的电脑自然就不一样。接下我们首先来创建一个低配版的LowConfigBuilder :

public class LowConfigBuilder implements ComputerConfigBuilder {

    private Computer mComputer;

    public LowConfigBuilder(){
        this.mComputer = new Computer();
    }
    @Override
    public void setCPU() {
        mComputer.setCPU("i5");
    }
    @Override
    public void setMemery() {
        mComputer.setMemory("4G");
    }
    @Override
    public void setHardDisk() {
        mComputer.setHardDisk("200G");
    }
    @Override
    public void setKeyboard() {
        mComputer.setKeyboard("有线键盘");
    }
    @Override
    public void setMouse() {
        mComputer.setMouse("有线鼠标");
    }
    @Override
    public Computer getComputer() {
        return mComputer;
    }
}

我们再来创建一个高配版的HighConfigBuider:

public class HighConfigBuider implements ComputerConfigBuilder {

    private Computer mComputer;

    public HighConfigBuider(){
        this.mComputer = new Computer();
    }
    @Override
    public void setCPU() {
        mComputer.setCPU("i7");
    }
    @Override
    public void setMemery() {
        mComputer.setMemory("16G");
    }
    @Override
    public void setHardDisk() {
        mComputer.setHardDisk("1T");
    }
    @Override
    public void setKeyboard() {
        mComputer.setKeyboard("机械键盘");
    }
    @Override
    public void setMouse() {
        mComputer.setMouse("无线鼠标");
    }
    @Override
    public Computer getComputer() {
        return mComputer;
    }
}

④最后我们再定义一个“装机人员”:

public class Director {
    private ComputerConfigBuilder mBuilder;
    public void setBuilder(ComputerConfigBuilder builder){
        this.mBuilder = builder;
    }
    public void createComputer(){
        mBuilder.setCPU();
        mBuilder.setMemery();
        mBuilder.setHardDisk();
        mBuilder.setKeyboard();
        mBuilder.setMouse();
    }
    public Computer getComputer(){
        return mBuilder.getComputer();
    }
}

我们只需要通过setBuilder来告诉“装机人员”需要什么配置,然后就可以通过createComputer来一步步组装电脑,组装完之后就可以调用getComputer方法来获取我们需要的电脑啦。

⑤运行 首先我们先创建一个低配版的电脑:

Director director = new Director();//创建装机人员
director.setBuilder(new LowConfigBuilder()); //告诉装机人员电脑配置,这里为低配版
director.createComputer(); //装机人员开始组装
Computer computer = director.getComputer(); //从装机人员获取组装好的电脑
System.out.print("电脑配置:" + computer.toString());  //查看电脑配置

此时我们就可以看到控制台上输出了低配版电脑的基本配置信息。 同理,咱们再创建一个高配版的电脑:

director.setBuilder(new HighConfigBuider()); //告诉装机人员电脑配置,这里为高配版
director.createComputer(); //装机人员开始组装
Computer computer = director.getComputer(); //从装机人员获取组装好的电脑
System.out.print("电脑配置:" + computer.toString()); //查看电脑配置

这就是经典的建造者模式,不过在我们日常开发中一般不常用经典的建造者模式,用的比较多的还是变种建造者模式,接下来由大聪明给大家娓娓道来。

(2)变种建造者模式

背景:大聪明上班的时候突然接到了老板的通知,老板需要大聪明创建一个不可变的Person对象,这个Person可以拥有以下几个属性:名字、性别、年龄、职业、车、鞋子、衣服、钱、房子。其中名字和性别是必须有的。

大聪明:小意思,分分钟搞定!

于是大聪明写出了一个Person类

public class Person {
    /*名字(必须)*/
    private final String name;
    /*性别(必须)*/
    private final String gender;
    /*年龄(非必须)*/
    private final String age;
    /*鞋子(非必须)*/
    private final String shoes;
    /*衣服(非必须)*/
    private final String clothes;
    /*钱(非必须)*/
    private final String money;
    /*房子(非必须)*/
    private final String house;
    /*汽车(非必须)*/
    private final String car;
    /*职业(非必须)*/
    private final String career;

    public Person(String name,String gender,String age,String shoes,String clothes,String money,String house,String car,String career){
        this.name = name;
        this.gender = gender;
        this.age = age;
        this.shoes = shoes;
        this.clothes = clothes;
        this.money = money;
        this.house = house;
        this.car = car;
        this.career = career;
    }

    public Person(String name, String gender){
        this(name,gender,null,null,null,null,null,null,null);
    }
}

由于要创建出的Person对象是不可变的,所以你将类中的属性都声明为final的,然后定义了一个参数为所有属性的构造方法,又因为name和gender为必须项,所以你为了调用者方便又单独定义了一个参数为name和gender的构造方法。于是大聪明把代码给了老板......

大聪明沾沾自喜ing......

老板:你别叫大聪明了!!叫大笨蛋吧!!!如果需要传入非必须属性的时候,这个构造方法调用起来不是很方便,因为这个构造方法参数太多了,很容易传错!!!

大聪明:难不住我,用set方法就好啦,看我的~

public class Person {
    /*名字(必须)*/
    private String name;
    /*性别(必须)*/
    private String gender;
    /*年龄(非必须)*/
    private String age;
    /*鞋子(非必须)*/
    private String shoes;
    /*衣服(非必须)*/
    private String clothes;
    /*钱(非必须)*/
    private String money;
    /*房子(非必须)*/
    private String house;
    /*汽车(非必须)*/
    private String car;
    /*职业(非必须)*/
    private String career;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    public String getShoes() {
        return shoes;
    }

    public void setShoes(String shoes) {
        this.shoes = shoes;
    }

    ......

}

如果需要创建对象的时候,直接用就好啦:

        Person person = new Person();
        person.setName("张三");
        person.setAge("22");
        person.setGender("男");
        person.setCareer("程序员");
        ......

老板:这样看上去确实是比较清晰了,只要创建一个对象,想要赋什么值直接set上去就可以了,不过用这个set方法,就违背了刚开始这个对象不可变的需求,其次用这种set方法一条一条赋值,逼格不够高,另外用这种方式很可能会得到一个不完整的Person对象,因为当你建完了Person对象,可能出于各方面的原因有些信息忘记set了,那么你得到的Person对象就不是你预期的对象。

大聪明:那我再想想......

two thousands years later~~~

大聪明终于想到了一个好办法,用变种建造者模式!

public class Person {
    /*名字(必须)*/
    private final String name;
    /*性别(必须)*/
    private final String gender;
    /*年龄(非必须)*/
    private final String age;
    /*鞋子(非必须)*/
    private final String shoes;
    /*衣服(非必须)*/
    private final String clothes;
    /*钱(非必须)*/
    private final String money;
    /*房子(非必须)*/
    private final String house;
    /*汽车(非必须)*/
    private final String car;
    /*职业(非必须)*/
    private final String career;


    private Person(Builder builder) {
        this.name = builder.name;
        this.gender = builder.gender;
        this.age = builder.age;
        this.shoes = builder.shoes;
        this.clothes = builder.clothes;
        this.money = builder.money;
        this.house = builder.house;
        this.car = builder.car;
        this.career = builder.career;
    }

    public static class Builder {
        private final String name;
        private final String gender;
        private String age;
        private String shoes;
        private String clothes;
        private String money;
        private String house;
        private String car;
        private String career;

        public Builder(String name,String gender) {
            this.name = name;
            this.gender = gender;
        }

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

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

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

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

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

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

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

        public Person build(){
            return new Person(this);
        }
    }


由于这个Person对象是不可变的,所以毫无疑问我们给他的所有属性都加了final修饰(如果没有不可变的需求也是可以不加的),然后在Person类中定义一个内部类Builder,这个Builder内部类中的属性要和Person中的相同,并且必须有的属性要用final修饰,防止这些属性没有被赋值,其他非必须的属性不能用final,因为如果加了final,就必须对其进行初始化。然后内部类中定义了一个构造方法,传入必须有的属性。其他非必须的属性都通过方法设置,每个方法都返回Builder对象自身。最后定义了一个build方法,将Builder对象传入Person的私有构造方法,最终返回一个对象。

下面我们看看如合创建一个Person对象:

        Person person = new Person.Builder("大聪明","男")
                .age("18")
                .money("9999999")
                .car("奔驰大G")
                .build();

于是大聪明兴冲冲的跑到老板办公室

大聪明:老板,你看是不是看上去逼格瞬间提高了,非必须的属性可以根据需要任意设置,非常灵活,而且这样先设置属性再创建对象,最终获取的对象一定是你预期的完整对象。

老板:哈哈哈哈,不愧是大聪明,果然名不虚传。

小结

当需要创建的对象具备复杂创建过程时,那么我们就可以选择建造者模式,我们就可以抽取出共性创建过程,然后交由具体实现类自定义创建流程,使得同样的创建行为可以生产出不同的产品,分离了创建与表示,使创建产品的灵活性大大增加。 对设计模式了解的小伙伴可以发现,建造者模式和工厂模式有一些相似之处,那具体是相似在哪里?又有哪写不同之处呢? 欲知后事如何,且听下回分解~

本人经验有限,有些地方可能讲的没有特别到位,如果您在阅读的时候想到了什么问题,欢迎在评论区留言,我们后续再一一探讨🙇‍

希望各位小伙伴动动自己可爱的小手,来一波点赞+关注 (✿◡‿◡) 让更多小伙伴看到这篇文章~ 蟹蟹呦(●'◡'●)

如果文章中有错误,欢迎大家留言指正;若您有更好、更独到的理解,欢迎您在留言区留下您的宝贵想法。

爱你所爱 行你所行 听从你心 无问东西