使用生成者模式来封装一个产品的构造过程,并允许按步骤来构建。
Intent 意图
当类日益庞大,里面涉及到初始化参数越来越多。如果还是使用原来的构造方法,会使得这个构造类日益庞杂,暴露了很多不应该暴露的细节。例如: ClassA(a1,a2,a3,b1,b2,b3....)
而大多数情况下,会面临以下情况:
- 部分初始化参数是可选的,并不是所有业务方都需要,使得构造函数不够简洁;
- 初始化参数过多,导致使用上出现混乱,例如以下的例子,在使用的时候,基本上是需要一个个去比对,校对参数是否使用正确。
public static String userSelectTagsWithScope(String name, String id, Boolean multiple, String htmlClass,
String selected, String placeholder, String scope, Boolean nullUser, Boolean anyUser,
Boolean emailUser, Boolean firstUser, Boolean currentUser, Object obj, String minAccessLevel,
String filterScope)
Motivation 动机
生成者模式通过将构造过程划分成一系列的固定步骤,以封装变与不变的部分,来解决构造函数变得臃肿以及使用上的方便。当需要创建不同形式的产物时, 其中的一些构造步骤可能需要不同构造者的实现。
Applicability 适用范围
- 当构造函数变得越来越庞杂且部分参数为可选的时候,可以使用生成者模式。生成者模式让你可以分步骤生成对象, 而且允许你仅使用必须的步骤。
- 当不同的两类产品有相似的构建步骤,只是具体的构建细节不同的时候,可以使用生成者模式。
Structure 结构
Participate 结构成员
结构中各个类、对象所扮演的角色
- Builder | 生成者: 声明了所有具体生成者中通用的构造步骤,具体的构造细节交给具体生成者实现。
- Concrete Builder | 具体生成者: 具体实现了 Builder 接口中的构造方法。
- Director(optional) | 主管类: 规定 Builder 调用构造的顺序以及具体使用哪个具体 Builder 实例来构建产品。当系统相对比较简单时,这个步骤可以省略,由 client 直接调用具体 Builder 即可。
Cooperation 协作
- Builder 需要明确定义出每个构建步骤或者构建流程,并交由具体生成者去实现具体构建细节。
- 不要忘记实现获取构造结果对象的方法。 由于返回的产品类型可能存在差异,所以无法在 Builder 接口声明处声明构造结果对象的方法,但如果返回产品存在一个公共基类,还是可以在 Builder 接口处声明这个返回产品类型声明或者只提供一个公共的返回产品方法即可。
- 只有在所有产品都遵循相同接口的情况下, 构造结果可以直接通过 Director 获取。 否则,生成者的构造结果只能通过生成者来获取。
Consequence 后果
-
Good
- 可以划分步骤来创建对象,或者递归进行对象创建
- 生成不同类型产品时,可以使用同一套流程
-
Bad
- 产品之间必须有相同点,只有如此才能抽取出一套固定流程
- 当存在多种类型时,构建类的数量增加会使得系统变得复杂
Implementation 示例
class House {
wall: number = 4;
door: number = 1;
room: number = 1;
hasGarden: boolean = false;
hasSwimPool: boolean = false;
setWall(wall: number) {
this.wall = wall;
}
setDoor(door: number) {
this.door = door;
}
setRooms(room: number) {
this.room = room;
}
setGarden() {
this.hasGarden = true;
}
setSwimPool() {
this.hasSwimPool = true;
}
display() {
console.log(`This house has ${this.wall} walls,${this.door} doors,${this.room} rooms.`);
if (this.hasSwimPool) {
console.log(' and with a swimming pool');
}
if (this.hasGarden) {
console.log(' and with a garden');
}
}
}
abstract class Builder {
house: House = new House();
abstract buildWall();
abstract buildDoor();
abstract buildRoom();
abstract buildGarden();
abstract buildSwimpool();
getHouse(): House {
return this.house;
}
reset() {
this.house = new House();
}
}
// big house
class BigBuilder extends Builder {
constructor() {
super();
}
buildWall() {
this.house.setWall(8);
}
buildDoor() {
this.house.setDoor(3);
}
buildRoom() {
this.house.setRooms(10);
}
buildGarden() {
this.house.setGarden();
}
buildSwimpool() {
this.house.setSwimPool();
}
}
// small house
class SmallBuilder extends Builder {
constructor() {
super();
}
buildWall() {
this.house.setWall(4);
}
buildDoor() {
this.house.setDoor(1);
}
buildRoom() {
this.house.setRooms(2);
}
buildGarden() {}
buildSwimpool() {}
}
class Director {
private builder: Builder;
public setBuilder(builder: Builder) {
this.builder = builder;
}
// 顺序执行内容,但是不关心里面的细节
public make() {
this.builder.buildWall();
this.builder.buildDoor();
this.builder.buildRoom();
this.builder.buildGarden();
this.builder.buildSwimpool();
}
public getBuilding() {
let house = this.builder.getHouse();
this.builder.reset();
return house;
}
}
function main() {
const director = new Director();
const bigHouseBuilder = new BigBuilder();
const smallHouseBuilder = new SmallBuilder();
director.setBuilder(bigHouseBuilder);
director.make();
const bigHouse = director.getBuilding();
bigHouse.display();
console.log('----------');
director.setBuilder(smallHouseBuilder);
director.make();
const smallHouse = director.getBuilding();
smallHouse.display();
}
main();
// console Output:
[Running] ts-node "/root/test/demo/pattern/23gof/05-builder/builder.ts"
This house has 8 walls,3 doors,10 rooms.
and with a swimming pool
and with a garden
----------
This house has 4 walls,1 doors,2 rooms.
Related Pattern 和其他模式直接的关系
-
生成者 Builder VS 工厂模式 Factory
工厂方法模式注重的是创建不同但是类型相关的整体对象的方法,而生成者模式注重的是部件构建的过程,旨在通过一步一步地精确构造创建出一个复杂的对象,通过设置不同的可选参数,定制化地创建不同的对象。
-
生成者模式 & 组合模式
两个模式可以组合使用,因为生成者模式的建造方法可以递归的方式使用,所以在构造复杂的组合树时,可以使用生成者模式。
-
生成者模式可以用单例模式来生成。
往期文章 index 【持续更新中】
Reference:
- refactoringguru.cn/design-patt…
- The Appendix Builder in Head First