设计模式(3):建造者模式BUILDER

建造者模式定义

意图

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

适用性

在以下情况使用Builder模式

  • 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。
  • 当构造过程必须允许被构造的对象有不同的表示时。

结构

此模式结构如下图所示
ShowImage

  • Builder

    – 为创建一个Product对象的各个部件指定抽象接口。

  • ConcreteBuilder

    – 实现Builder的接口以构造和装配该产品的各个部件。

    – 定义并明确它所创建的表示。

    – 提供一个检索产品的接口。

  • Director

    – 构造一个使用Builder接口的对象。

  • Product

    – 表示被构造的复杂对象。ConcreteBuilder创建该产品的内部表示并定义它的装配过程。

    – 包含定义组成部件的类,包括将这些部件装配成最终产品的接口。

协作

  • 客户创建Director对象,并用它所想要的Builder对象进行配置。
  • 一旦产品部件被生成,Director就会通知Builder。
  • Builder处理Director的请求,并将部件添加到该产品中。
  • 客户从Builder中检索产品。
    下面的交互图展示了Builder和Director是如何与一个client协作的
    ShowImage

效果

  1. 它使你可以改变一个产品的内部表示

    Builder对象提供给导向器一个构造产品的抽象接口。该接口使得生成器可以隐藏这个产品的表示和内部结构。它同时也隐藏了该产品是如何装配的。因为产品是通过抽象接口构造的,你在改变该产品的内部表示时所要做的只是定义一个新的生成器。

  2. 它将构造代码和表示代码分开

    Builder模式通过封装一个复杂对象的创建和表示方式提高了对象的模块性。客户不需要知道定义产品内部结构的类的所有信息;这些类是不出现在Builder接口中的。每个ConcreteBuilder包含了创建和装配一个特定产品的所有代码。这些代码只需要写一次;然后不同的Director可以复用它以在相同部件集合的基础上构作不同的Product。

  3. 它使你可对构造过程进行更精细的控制

    Builder模式与一下子就生成产品的创建型模式不同,它是在导向者的控制下一步一步构造产品的。仅当该产品完成时导向者才从生成器中取回它。因此Builder接口相比其他创建型模式能更好的反映产品的构造过程。这使你可以更精细的控制构建过程,从而能更精细的控制所得产品的内部结构。

建造者模式实现(Implement)

在之前读《Effetive Java》时里面有提到“遇到多个构造器参数时考虑用构造器”,之前也总结了一下:链接

该文主要探讨了Builder模式对于构造器参数比较多的时候的意义,但是在对构造过程进行精细的控制时,Builder模式也能发挥自己的作用。

案例

先来看现实的一个场景,日常生活中汽车随处可见,现在汽车厂商Benz和Audi要制造汽车,来看下用Builder模式是怎么实现的。

代码实现

先定义我们的Product,Car的抽象类,汽车包含发动机,车轮,车载系统和品牌四个部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public abstract class Car {
protected String engine;
protected String wheel;
protected String system;
protected String brand;
protected Car() {
}
//设置轮子
public void setWheel(String wheel) {
this.wheel = wheel;
}
//设置发动机
public abstract void setEngine();
//设置车载系统
public abstract void setSystem();
//贴牌
public abstract void setBrand();
@Override
public String toString() {
return "Car (brand = " + brand + ", system = " + system + ", engine = " + engine + ", wheel = " + wheel + ")";
}
}

具体的BenzCar类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class BenzCar extends Car {
protected BenzCar() {
}
@Override
public void setBrand() {
this.brand = "Benz";
}
@Override
public void setEngine() {
this.engine = "Benz Engine";
}
@Override
public void setSystem() {
this.system = "Benz System";
}
}

接下来,我们定义抽象的CarBuilder类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public abstract class CarBuilder {
//安装发动机
public abstract void buildEngine();
//安装车载系统
public abstract void buildSystem();
//安装轮子
public abstract void buildWheel(String wheel);
//贴牌
public abstract void buildBrand();
//装配
public abstract Car build();
}

现在需要一个具体的builder实现类BenzCarBuilder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class BenzCarBuilder extends CarBuilder {
private Car benzCar = new BenzCar();
@Override
public void buildEngine() {
benzCar.setEngine();
}
@Override
public void buildSystem() {
benzCar.setSystem();
}
@Override
public void buildBrand() {
benzCar.setBrand();
}
@Override
public void buildWheel(String wheel) {
benzCar.setWheel(wheel);
}
@Override
public Car build() {
return benzCar;
}
}

还有最后一个关键角色Director

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class CarDirector {
private CarBuilder carBuilder;
public CarDirector(CarBuilder carBuilder) {
this.carBuilder = carBuilder;
}
/**
* 详细的装配过程
*/
public void construct(String wheel) {
carBuilder.buildEngine();
carBuilder.buildSystem();
carBuilder.buildBrand();
carBuilder.buildWheel(wheel);
}
}

装配过程的代码

1
2
3
4
5
6
7
8
9
10
@Test
public void builderTest1() {
CarBuilder carBuilder = new BenzCarBuilder();
//Director
CarDirector benzDirector = new CarDirector(carBuilder);
benzDirector.construct("米其林轮胎");
BenzCar benzCar1 = (BenzCar) carBuilder.build();
System.out.println(benzCar1.toString());
}

现在对上面几段代码进行详细的分析,Director在这里充当的是指挥者的角色,Director本身并不参与创建对象,construct()方法都是在指挥builder来装配对象,而我们调用的时候直接使用Director进行创建BenzCar就行了,调用者并不需要知道build的过程细节是什么,这里也体现出builder模式的优点,将产品表示和构造分离了。除此之外,我们可以在Director中的construct()方法按自己的需求对构造过程进行修改,这也体现出builder模式的优点。

实际使用有时候会把Director省略,在builderTest2()和builderTest3()方法中就省略了Director,而且完全可以自定义装配的过程,比如先装配发动机还是先装配车载系统。代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
public void builderTest2() {
//有时候会省略Director,直接使用builder
AudiCar audiCar1 = new AudiCar.AudiCarBuilder().buildEngine()
.buildSystem()
.buildBrand()
.buildWheel("倍耐力轮胎")
.build();
System.out.println(audiCar1.toString());
}
@Test
public void builderTest3() {
//修改AudiCar的装配过程,先装配系统再装配发动机
AudiCar audiCar2 = new AudiCar.AudiCarBuilder().buildSystem()
.buildEngine()
.buildWheel("倍耐力轮胎")
.buildBrand()
.build();
System.out.println(audiCar2.toString());
}

总结

本文对Builder模式进行了介绍,经过分析后发现Builder模式具有以下几个优点:

  • 将产品自身与构造过程解耦,客户端并不需要知道产品内部组成的细节
  • builder之间没有任何关联,使用不同的builder可以产生不同的产品,而且可以增加新的builder来扩展
  • 可以精细地控制产品的整个创建过程
  • 链式的方法调用结构清晰,虽然代码量有所增加,但是代码可读性明显改善

参考代码

------ 本文结束 ------

版权声明


BillyYccc's blog by Billy Yuan is licensed under a Creative Commons BY-NC-SA 4.0 International License.
本文原创于BillyYccc's Blog,转载请注明原作者及出处!