建造者模式

89 阅读5分钟

建造者模式

将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示。

可以想象成一个机器,其内由各种小组件组成,如电脑的主机一样。

建造者模式分离了部件的构造(由Builder来负责)和搭配(由Director负责)。从而可以构造出复杂的对象。

由于实现了构建和装配的解耦,不同的构建器,相同的装配,也可以做出不同的对象;相同的构建器,不同的装备顺序也可以做出不同的对象,也就是实现了构建算法、装配算法的解耦,实现了更好的复用。

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

结构

  • 抽象建造者类(Builder)——这个接口规定要实现复杂对象的哪些部分的创建,并不涉及具体的对象部件的创建。
  • 具体建造者类(ConcreteBuilder)——实现Builder接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供产品的实例。
  • 产品类(Product)——要创建的复杂对象。
  • 指挥者类(Director)——调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建。

UML.png

🌰举个栗子

我们现在要生产共享单车,自行车中有车架、车座等组件。车架有碳纤维、铝合金材质之分,车座又有橡胶、真皮材质之分。我们对此过程使用建造者模式。

我们设计类图如下:

BikeUML.png

这里Bike指产品,包含车架、车座等组件 Builder是抽象建造者 MobikeBuilder和OfoBuilder是具体的建造者 Director是指挥者

我们构建各个类:

//Bike.class,定义其内部组件
private String frame;
​
private String seat;
​
//setter、getter...
//Builder.class 该类定义为抽象类,定义建造者所需的抽象方法,以及bike类型变量,为此后建造者的实现类定义规范
public abstract class Builder {
    protected Bike bike = new Bike();
    //创建各组件
    public abstract void buildFrame();
    public abstract void buildSeat();
    //构建自行车
    public abstract Bike createBike();
}
//MobikeBuilder.class,该类继承自Buider抽象类,为创建者的实现类,其内实现Builder中的三个方法,组装自行车public void builderFrame() {
    //因为bike是抽象类中定义好的,所以该类中自带bike属性,我们可以直接拿来用
    bike.setFrame("碳纤维车架");
}
​
public void buildSeat() {
    bike.setSeat("真皮车座");
}
​
public Bike createBike() {
    return bike;
}

OfoBuilder同理,这里不再赘述

//Director.class,顾名思义,用来指挥全局,我们把builder类的实现类传给它 ,让它拿着去创建组装自行车//声明builder类型的变量
private Builder builder;
​
public Director(Builder builder) {
    this.builder = builder;
}
​
//组装自行车的功能
public Bike construct() {
    builder.buildFrame();
    builder.buildSeat();
    return builder.createBike();
}

最后,我们去测试类中组装自行车:

//Client.class
//创建指挥者对象
Director director = new Director(new MobileBuilder);
//让指挥者指挥组装自行车
Bike bike = director.construct();

这样我们就成功组建好了自行车!

注:上面的示例是Builder模式的常规用法,指挥者类Director在建造者模式中具有很重要的作用 ,它用于指导构建者如何构建产品,控制调用先后次序,并向调用者返回完整的产品类,但是有些情况下需要简化系统结构,可以把指挥者类和抽象建造者进行结合!

也就是说,我们本例中可以将Director类中组装自行车的construct方法写到Builder抽象类中

但这样做同时也加重了抽象建造者类的职责,也不太符合单一职责原则,如果construct()过于复杂,建议还是封装到Director中去!

优缺点

优点:

  • 封装性好,在使用该模式的场景中,一般产品类是比较稳定的,所以,将主要的业务逻辑封装在指挥者类中对整体而言可以取得比较好的稳定性
  • 客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象
  • 可以更加精细地控制产品的创建过程
  • 扩展性好,符合开闭原则

缺点:

  • 如果产品之间的差异性很大,那么就不适合使用建造者模式,因此其使用范围受到一定限制。

使用场景

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

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

扩展

当一个类中的参数过多时,会变成这样:

Phone phone = new Phone("intel", "三星", "金士顿", "华硕");

将参数一股脑传入进去,这样的代码可读性很差!

所以我们需要结合创建者模式进行改进:

//Phone.lass//需要的参数
private String cpu;
private String screen;
private String memory;
private String mainboard;
​
//构造方法私有化
private Phone(Builder) {
    cpu = builder.cpu;
    screen = builder.screen;
    memory = builder.memory;
    mainboard = builder.mainboard
}
​
//创建内部类Builder
public static final class Builder {
    private String cpu;
    private String screen;
    private String memory;
    private String mainboard;
    
    //填充各个组件
    public Builder cpu(String cpu) {
        this.cpu = cpu;
        return this;
    }
    
    public Builder cpu(String screen) {
        this.screen = screen;
        return this;
    }
    
    public Builder memory(String memory) {
        this.memory = memory;
        return this;
    }
    
    public Builder mainboard(String mainboard) {
        this.mainboard = mainboard;
        return this;
    }
    
    public Phone build() {
        return new Phone(this)
    }
}

这样我们就可以在创建时使用如下形式:

//client.class
Phone phone = new Phone.Builder()
        .cpu("intel")
        .screen("三星屏幕")
        .memory("金士顿内存条")
        .mainboard("华硕主板")
        .build()

如此一来,可对参数一目了然,且我们可以任意选定构建顺序!