【深入设计模式】建造者模式—带你彻底弄懂建造者模式

在日常生活中,我们在烹饪一道菜、制作一款手工艺品或者生产一款手机、一台汽车、一架飞机等等,这些东西在制造过程中的工艺流程都非常复杂,购买者并不关心其是如何生成的,而生产者会根据产品的需求,虽然产品大致的生产过程及组件都是相通的,但是不同型号的产品对应的某些组件可能不一样,这个时候就需要产品生产方能够根据消费者的选择,提供一个灵活的方法来生产需求的产品,这个时候就可以使用建造者模式来进行生产。

1.建造者模式

1.1 建造者模式简介

在开发中,有时候我们需要创建出一个很复杂的对象,这个对象的创建有一个固定的步骤,并且每个步骤中会涉及到多个组件对象,这个时候就可以考虑使用建造者模式。使用建造者模式将原本复杂的对象创建过程按照规律将其分解成多个小步骤,这样在构建对象时可以灵活的选择或修改步骤。建造者模式将对象的创建和表示过程进行分离,这样我们可以使用同样的过程,只需修改这个过程中的小步骤,便能够构建出不同的对象。而对于调用方来说,我们只需要传入需要构建的类型,便能够得到需要的对象,并不需要关系创建的过程,从而实现解耦。

1.2 建造者模式结构

建造者模式的结构需要产品(Product)、抽象建造者(Builder)、具体建造者(ConcreteBuilder)、指挥者(Director)四个角色。

  • 产品(Product):表明需要构建的产品对象
  • 抽象建造者(Builder):抽象出来的构建者类,用于定义创建对象所需的步骤以及创建的步骤的调用过程
  • 具体建造者(ConcreteBuilder):抽象建造者的具体实现,对于不同的创建过程可以用不同的类进行实现
  • 指挥者(Director):使用 Builder 的类,提供给调用方使用,调用方通过使用指挥者来获取产品


    建造者模式结构
// 产品类,定义产品的三个部分
public class Product {
    private Object part1;
    private Object part2;
    private Object part3;

    public void setPart1(Object part1) {
        this.part1 = part1;
    }

    public void setPart2(Object part2) {
        this.part2 = part2;
    }

    public void setPart3(Object part3) {
        this.part3 = part3;
    }

    @Override
    public String toString() {
        return "Product{" +
                "part1=" + part1 +
                ", part2=" + part2 +
                ", part3=" + part3 +
                '}';
    }
}

// 抽象建造者类,构建了一个产品对象,并定义了构建产品三个部分所需要的三个方法以及获取产品的方法
public abstract class Builder {
    protected Product product = new Product();

    public abstract void buildPart1();
    public abstract void buildPart2();
    public abstract void buildPart3();
    public abstract Product getProduct();
}

// 具体建造者 1
public class ConcreteBuilder1 extends Builder{
    @Override
    public void buildPart1() {
        product.setPart1("builder 1 set part 1.");
    }

    @Override
    public void buildPart2() {
        product.setPart2("builder 1 set part 2.");
    }

    @Override
    public void buildPart3() {
        product.setPart3("builder 1 set part 3.");
    }

    @Override
    public Product getProduct() {
        System.out.println("builder 1 build product.");
        return product;
    }
}

// 具体建造者 2
public class ConcreteBuilder2 extends Builder {
    @Override
    public void buildPart1() {
        product.setPart1("builder 2 set part 1.");
    }

    @Override
    public void buildPart2() {
        product.setPart2("builder 2 set part 2.");
    }

    @Override
    public void buildPart3() {
        product.setPart3("builder 2 set part 3.");
    }

    @Override
    public Product getProduct() {
        System.out.println("builder 2 build product.");
        return product;
    }
}

// 指挥者对象
public class Director {
    private Builder builder;

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

    public Product construct() {
        builder.buildPart1();
        builder.buildPart2();
        builder.buildPart3();
        Product product = builder.getProduct();
        return product;
    }
}

下面是调用方的代码

public static void main(String[] args) {
    Director director1 = new Director(new ConcreteBuilder1());
    Product product1 = director1.construct();
    System.out.println(product1);
    System.out.println("==================================");

    Director director2 = new Director(new ConcreteBuilder2());
    Product product2 = director2.construct();
    System.out.println(product2);
}

控制台打印结果如下:

builder 1 build product.
Product{part1=builder 1 set part 1., part2=builder 1 set part 2., part3=builder 1 set part 3.}
==================================
builder 2 build product.
Product{part1=builder 2 set part 1., part2=builder 2 set part 2., part3=builder 2 set part 3.}

我们可以看到通过给 Director 对象传入具体的构建者便能够构建出不同的产品对象,并且构建过程对于调用方来说是不可见的。

1.3 建造者模式示例

按照惯例使用一个具体示例来加深对建造者模式的印象。我们以组装电脑主机为例,一台电脑主机通常都需要 cpu 、内存条、主板、显卡、硬盘组成,虽然组成都是一致的,但是不同价位所能达到的配置就完全不一样了,这个时候就可以使用建造者模式来模拟组装电脑。我们需要组装一台游戏电脑和一台办公电脑,那么按照上面的建造者模式结构,就需要以下几个类:

  • 产品电脑类(Computer):定义了一台电脑所需要的几个组件:CPU、显卡、内存、主板和硬盘
  • 抽象电脑建造者(ComputerBuilder):定义一个产品电脑及组装电脑所需配件方法
  • 游戏电脑建造者(GameComputerBuilder):继承抽电脑电脑类,并实现抽象方法选择所需配件
  • 办公电脑建造者(OfficeComputerBuilder):继承抽电脑电脑类,并实现抽象方法选择所需配件
  • 组装电脑指挥者(ComputerDirector):在内部进行电脑组装,并给调用方返回一台组装好的电脑

具体代码如下:

// 产品电脑类
public class Computer {
    private String CPU;
    private String GPU;
    private String memory;
    private String motherboard;
    private String hardDisk;


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

    public void setGPU(String GPU) {
        this.GPU = GPU;
    }

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

    public void setMotherboard(String motherboard) {
        this.motherboard = motherboard;
    }

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

    @Override
    public String toString() {
        return "you have a computer:\n" +
                "\t CPU: " + CPU + "\n" +
                "\t GPU: " + GPU + "\n" +
                "\t memory: " + memory + "\n" +
                "\t motherboard: " + motherboard + "\n" +
                "\t hardDisk: " + hardDisk + "\n";
    }
}

// 抽象电脑建造者
public abstract class ComputerBuilder {
    protected Computer computer = new Computer();

    public abstract void CPU();

    public abstract void GPU();

    public abstract void memory();

    public abstract void motherboard();

    public abstract void hardDisk();

    public abstract Computer getComputer();
}

// 游戏电脑建造者
public class GameComputerBuilder extends ComputerBuilder {
    @Override
    public void CPU() {
        computer.setCPU("i9-12900K");
    }

    @Override
    public void GPU() {
        computer.setGPU("RTX 3090 Ti");
    }

    @Override
    public void memory() {
        computer.setMemory("64GB");
    }

    @Override
    public void motherboard() {
        computer.setMotherboard("Z590 AORUS MASTER");
    }

    @Override
    public void hardDisk() {
        computer.setHardDisk("2TB SSD");
    }

    @Override
    public Computer getComputer() {

        return computer;
    }
}

// 办公电脑建造者
public class OfficeComputerBuilder extends ComputerBuilder {
    @Override
    public void CPU() {
        computer.setCPU("i7-7700k");
    }

    @Override
    public void GPU() {
        computer.setGPU("GTX 1050 Ti");
    }

    @Override
    public void memory() {
        computer.setMemory("32GB");
    }

    @Override
    public void motherboard() {
        computer.setMotherboard("ASUS  B560M-PLUS");
    }

    @Override
    public void hardDisk() {
        computer.setHardDisk("1TB SSD");
    }

    @Override
    public Computer getComputer() {
        return computer;
    }
}

// 组装电脑指挥者
public class Director {
    private Builder builder;

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

    public Product construct() {
        builder.buildPart1();
        builder.buildPart2();
        builder.buildPart3();
        Product product = builder.getProduct();
        return product;
    }
}

我们需要组装一台游戏电脑和办公电脑,通常情况下虽然电脑的组装所需部件都是一样的,但是游戏电脑的配置性能都会比办公电脑高,因此需要两个不同的具体构建者来给电脑的配置进行选择,调用方的代码如下:

public static void main(String[] args) {
    ComputerDirector computerDirector = new ComputerDirector(new GameComputerBuilder());
    Computer gameComputer = computerDirector.construct();
    System.out.println(gameComputer);
    System.out.println("==================================");
    ComputerDirector computerDirector1 = new ComputerDirector(new OfficeComputerBuilder());
    Computer officeComputer = computerDirector1.construct();
    System.out.println(officeComputer);
}

执行上面代码,控制台打印如下:

you have a computer:
     CPU: i9-12900K
     GPU: RTX 3090 Ti
     memory: 64GB
     motherboard: Z590 AORUS MASTER
     hardDisk: 2TB SSD

==================================
you have a computer:
     CPU: i7-7700k
     GPU: GTX 1050 Ti
     memory: 32GB
     motherboard: ASUS  B560M-PLUS
     hardDisk: 1TB SSD

从上面的代码可以看到,在调用方通过传入不同的电脑建造者,而获取到了两台不同配置的电脑。

2. 建造者模式在框架源码中的应用

2.1 建造者模式在 JDK 中的应用

JDK 中的建造者模式使用最多的就是 StringBuilder 类,StringBuilder 继承自 AbstractStringBuilder,而我们每次在调用 append 方法的时候就是在往 AbstractStringBuilder 类中变量 value 中追加字符,所以此时 AbstractStringBuilder 就对应抽象建造者,StringBuilder 就是具体的建造者,String 对象就是我们所需要的产品。但是此时我们并没有发现 Director,其实此时的 StringBuilder 类同时也充当着 Director 的角色,其 toString() 方法就是返回最终 String 对象。

abstract class AbstractStringBuilder implements Appendable, CharSequence {
    /**
     * The value is used for character storage.
     */
    char[] value;

    /**
     * The count is the number of characters used.
     */
    int count;
    
    ……
        
    @Override
    public AbstractStringBuilder append(char c) {
        ensureCapacityInternal(count + 1);
        value[count++] = c;
        return this;
    }
    ……
}
public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence
{
    ……

    @Override
    public StringBuilder append(char c) {
        super.append(c);
        return this;
    }

    }
    
    ……
    @Override
    public String toString() {
        // Create a copy, don't share the array
        return new String(value, 0, count);
    }
}

2.2 Lombok 中的 @Builder

我们在使用 Lombok 时有个基础注解叫 @Builder,使用该注解的类就不用再 new 对象,直接赋值然后调用 build() 方法便能构建对象。其原理就是在使用该注解的类中生成一个静态的内部 Builder类,然后通过调用该内部类的方法给生成的类对象赋值。我们以 Computer 类为例,代码如下:

@Builder
public class Computer {
    private String CPU;
    private String GPU;
    private String memory;
    private String motherboard;
    private String hardDisk;

}

对代码进行编译后,再看最后生成的代码如下

public class Computer {
    private String CPU;
    private String GPU;
    private String memory;
    private String motherboard;
    private String hardDisk;

    Computer(String CPU, String GPU, String memory, String motherboard, String hardDisk) {
        this.CPU = CPU;
        this.GPU = GPU;
        this.memory = memory;
        this.motherboard = motherboard;
        this.hardDisk = hardDisk;
    }

    public static Computer.ComputerBuilder builder() {
        return new Computer.ComputerBuilder();
    }

    public static class ComputerBuilder {
        private String CPU;
        private String GPU;
        private String memory;
        private String motherboard;
        private String hardDisk;

        ComputerBuilder() {
        }

        public Computer.ComputerBuilder CPU(String CPU) {
            this.CPU = CPU;
            return this;
        }

        public Computer.ComputerBuilder GPU(String GPU) {
            this.GPU = GPU;
            return this;
        }

        public Computer.ComputerBuilder memory(String memory) {
            this.memory = memory;
            return this;
        }

        public Computer.ComputerBuilder motherboard(String motherboard) {
            this.motherboard = motherboard;
            return this;
        }

        public Computer.ComputerBuilder hardDisk(String hardDisk) {
            this.hardDisk = hardDisk;
            return this;
        }

        public Computer build() {
            return new Computer(this.CPU, this.GPU, this.memory, this.motherboard, this.hardDisk);
        }

        public String toString() {
            return "Computer.ComputerBuilder(CPU=" + this.CPU + ", GPU=" + this.GPU + ", memory=" + this.memory + ", motherboard=" + this.motherboard + ", hardDisk=" + this.hardDisk + ")";
        }
    }
}

可以看到反编译后内部生成了 ComputerBuilder 类,该类就是用于构建 Computer 对象,因此这也是一个构建者模式。

3. 总结

建造者模式将对象的创建过程和表现分离,并且调用方通过指挥者调用方法对对象进行构建,使得调用方不再关心对象构建过程,构建对象的具体过程可以根据传入类型的不同而改变。通常在实际开发应用中通常会对建造者模式的角色进行阉割,往往只保留真正构建对象的过程。那么有的人就可能会有疑问了,构建者模式最终是获取一个对象,工厂模式也是获取一个对象,这两种模式有什么区别呢?这两种模式都是属于创建型模式,而这两种模式的侧重点不太一样:

  • 建造者模式用于复杂对象的构建,并且对象中的复杂组件的调用和赋值过程能够自定义;工厂模式所创建出来的对象都是以严格的
  • 建造者模式在构建的过程需要知道对象需要哪些组件,并对组件进行赋值;工厂模式并不关心组件,直接调用方法即可
  • 从建造者模式的概念上讲,其实建造者模式是在一个固定的流程下构建的对象,因此强调一定的执行顺序,而工厂模式并不关心。但是在开发中往往会忽略掉这部分的执行顺序

两种模式在实际开发中的使用需要根据所需场景进行抉择。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容