建造者模式

前言

在正式学习建造者模式之前,我一直把那种链式编程当成是建造者模式,类似obj.set(“A”).set(“B”).set(“C”).build();这种,后来认真的去研究了一下,发现并不是我想象的那样,怎么说呢,对但不完全对。为了真正搞懂建造者模式,也是查阅了大量的资料,发现网上包括很多视频也是讲的比较片面,有的甚至是错的,很容易误导初学者,所以特此记录一下。

建造者模式

基本概念

建造者模式(Builder Pattern)将一个复杂对象的构建和它的表示分离,使同样的构建过程可以创建不同的表示。

不知道各位看这个概念明白没有,反正我第一次是没看懂,看图解释一下吧。

主机示意图

上图左边是电脑的各个部件,包括CPU、主板、硬盘、内存条,右边是完整的主机。主机就可以看作是复杂的对象,我们组装电脑的过程就叫复杂对象的构建,分离其实就是解耦合;组装电脑的时候可以使用不同型号的部件去组装,比如CPU有Intel和AMD,主板有华硕和联想等等,虽然最后组装好的产品都是主机,但是主机的性能肯定是不一样的,这就叫同样的构建过程可以创建不同的表示。

结构

建造者模式包含如下角色:

  • Product(产品):具体产品对象
  • Builder(抽象建造者):创建一个产品各个部件的接口
  • ConcreteBuilder(具体建造者):实现抽象建造者对应的接口
  • Director(指挥者):创建一个复杂的对象,控制具体的流程

代码实现

  1. 先创建一个产品
public class Computer {
    private String board; // 主板
    private String cpu; // cpu
    private String disk; // 硬盘
    private String memory; // 内存条
    // 省略get/set/toString方法 
}
  1. 创建一个抽象建造者
public abstract class ComputerBuilder {
  	// 构建主机的抽象部件
    public abstract void board();
    public abstract void cpu();
    public abstract void disk();
    public abstract void memory();

  	// 构建主机的抽象方法
    public abstract Computer buildComputer();
}
  1. 有了抽象类,就得有对应的实现,创建一个具体建造者实现类
public class LenovoComputer extends ComputerBuilder{
  	// 具体建造者创建产品
  	private Computer computer = new Computer();
  
    @Override
    public void board(String board) {
        computer.setBoard(board);
    }
    @Override
    public void cpu(String cpu) {
        computer.setCpu(cpu);
    }
    @Override
    public void disk(String disk) {
        computer.setDisk(disk);
    }
    @Override
    public void memory(String memory) {
        computer.setMemory(memory);
    }
    @Override
    public Computer buildComputer() {
        return computer;
    }
}
  1. 最后创建一个指挥者类,Director类的主要作用是调用具体的Builder,来构建对象的各个部分,Director类起到封装作用,避免高层模块深入到建造者内部的实现类。
public class Director {
  	private ComputerBuilder computerBuilder;

    public Director(ComputerBuilder computerBuilder) {
        this.computerBuilder = computerBuilder;
    }
    // 指挥者设置流程的先后顺序,返回具体产品
    public Computer build(String board, String cpu, String disk, String memory){
        computerBuilder.board(board);
        computerBuilder.cpu(cpu);
        computerBuilder.disk(disk);
        computerBuilder.memory(memory);
        return computerBuilder.buildComputer();
    }
}
  1. 客户端验证
public class Client {
    public static void main(String[] args) {
        // 具体的建造者
        ComputerBuilder builder = new LenovoComputer();
        // 指挥者
        Director director = new Director(builder);
        // 得到具体的产品
        Computer computer = director.build("联想主板", "Intel处理器", "联想硬盘", "DDR3内存条");
        System.out.println(computer.toString());
    }
}

输出结果:

Computer{board='联想主板', cpu='Intel处理器', disk='联想硬盘', memory='DDR3内存条'}

从上面的代码可以看到,Computer产品是通过LenovoComputer构建的,Director封装了构建复杂产品对象的过程,对外隐藏了构建细节。ComputerBuilder和Director一起将一个复杂的对象的构建与表示分离。

建造者模式的优缺点

优点:

  • 隐藏了产品创建的细节

​ 客户端不必知道产品内部细节,将产品本身与产品创建过程解耦,使得相同的创建过程可以创建出不同的产品对象。

  • 可以更加精细地控制产品的创建过程

​ 将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。

  • 容易扩展

​ 如果有新需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前的代码,符合开闭原则。

缺点:

建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间差异很大,则不适合使用建造者模式,因此其使用范围受限制。

使用场景

建造者模式创建的是复杂的对象,其产品的各个部分经常面临着剧烈的变化,但将它们组合在一起的算法却相对稳定,所以它通常在以下场合使用。

  1. 创建的对象较复杂,由多个部件构成,各部件面临着复杂的变化,但部件间的建造顺序是稳定的;
  2. 创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立的。

模式扩展

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

重构前代码如下:

public class MacBook {
    private String board; // 主板
    private String cpu; // cpu
    private String disk; // 硬盘
    private String memory; // 内存条

    public MacBook(String board, String cpu, String disk, String memory) {
        this.board = board;
        this.cpu = cpu;
        this.disk = disk;
        this.memory = memory;
    }
  	// 省略get/set/toString方法
}
public class Client {
    public static void main(String[] args) {
        // 重构前
        MacBook macBook = new MacBook("苹果主板","M1处理器", "苹果硬盘","苹果内存条");
        System.out.println(macBook.toString());
    }
}

上面在客户端代码中构建MacBook对象,传递了四个参数,如果参数更多呢?代码的可读性及使用成本就会很高。

重构后代码:

public class MacBook {
    private String board; // 主板
    private String cpu; // cpu
    private String disk; // 硬盘
    private String memory; // 内存条

    private MacBook(Builder builder) {
        this.board = builder.board;
        this.cpu = builder.cpu;
        this.disk = builder.disk;
        this.memory = builder.memory;
    }

    public static class Builder {
        private String board;
        private String cpu;
        private String disk;
        private String memory;

        public Builder setBoard(String board) {
            this.board = board;
            return this;
        }
        public Builder setCpu(String cpu) {
            this.cpu = cpu;
            return this;
        }
        public Builder setDisk(String disk) {
            this.disk = disk;
            return this;
        }
        public Builder setMemory(String memory) {
            this.memory = memory;
            return this;
        }
        public MacBook build() {
            return new MacBook(this);
        }
    }
    // 省略toString方法
}

客户端验证:

public class Client {
    public static void main(String[] args) {
        MacBook macBook = new MacBook.Builder()
                .setBoard("苹果主板")
                .setCpu("M1处理器")
                .setDisk("苹果硬盘")
                .setMemory("苹果内存条")
                .build();
        System.out.println(macBook.toString());
    }
}

输出结果:

MacBook{board='苹果主板', cpu='M1处理器', disk='苹果硬盘', memory='苹果内存条'}

重构后的代码在使用起来更方便,某种程度上也可以提高开发效率。

这就是我开头提到的链式编程,当初一直把这个误以为就是建造者模式。一定要分清楚,这只是建造者模式的一种扩展用法,标准的写法是最开始那种。

有态度的总结

  1. 建造者模式可以将构建和装配解耦,一步一步创建一个复杂的对象,它也属于创建型模式;

  2. 工作中如果构造对象的参数特别多,常使用建造者模式对构造函数进行优化。

OK,建造者模式讲完了,我们下期见~