建造者模式(Builder)所构建的对象一定是庞大而复杂的,并且一定是按照既定的制造工序将组件组装起来的,例如计算机、汽车、建筑物等。我们通常将负责构建这些大型对象的工程师称为建造者。建造者模式又称为生成器模式,主要用于对复杂对象的构建、初始化,它可以将多个简单的组件对象按顺序一步步组装起来,最终构建成一个复杂的成品对象。与工厂系列模式不同的是,建造者模式的主要目的在于把烦琐的构建过程从不同对象中抽离出来,使其脱离并独立于产品类与工厂类,最终实现用同一套标准的制造工序能够产出不同的产品。
建造步骤的重要性
在开始实战之前我们首先得搞清楚建造者面对着什么样的产品模型。以典型的角色扮演类网络游戏为例,在开始游戏之前玩家通常可以选择不同的角色。为了让人物鲜活起来,不同的游戏角色应该有其独特的产品特性。
玩家选定角色后需要对其进行初始化,假设整个过程分3个步骤完成。第一步,玩家需要为角色选择形象以及分配力量、灵力、体力、敏捷等属性值,这也是游戏人设中最为重要的一个环节;第二步,玩家可以为角色配备不同的衣服或铠甲,低于所需力量值的铠甲则不能穿戴;第三步,玩家选择手持的武器与盾牌,它同上一步一样需要满足一定的条件。显然,每个角色都是按照这个流程完成初始化的,否则游戏就无法进行下去,例如如果在没有分配角色属性值的前提下就先进行武器选择,那么缺乏力量的角色根本无法配备任何装备或者武器;如果让缺少灵力的战士戴上魔法帽或是让力量弱小的法师手持重型武器,就会导致游戏角色出现不可预知的混乱。
成型的游戏角色是依靠角色对象、装备对象组装而成的,对于这种复杂对象的构建一定要依赖建造者来完成。除此以外,若要避免混乱情况的发生,建造者的制造过程不仅要分步完成,还要按照顺序进行,所以建造者的各制造步骤与逻辑都应该被抽离出来独立于数据模型,复杂的游戏角色设定还需交给专业的建造团队去完成。
地产开发商的困惑
秉承我们一贯奉行的简单直观的宗旨,既然是建造者,我们就以建筑物建造为例来进行代码实战。盖房子可不能开玩笑,为了保证质量,我们绝不能允许豆腐渣工程出现,所以严谨的设计与施工流程的把控是不可或缺的,否则可能会房倒屋塌、家毁人亡。首先,建筑物本身应该由多个组件组成,且各组件按一定工序建造,缺一不可
建筑物的组件建造是相当复杂的,为了简化其数据模型,我们将组成建筑物的模块归纳为3个组件,分别是地基、墙体、屋顶,将它们组装起来就能形成一座建筑物
package Builder;
import java.util.ArrayList;
import java.util.List;
public class Building {
//使用List来模拟建筑物组件的组装
private List<String> buildingComponents = new ArrayList<>();
//地基
public void setBasement(String basement){
this.buildingComponents.add(basement);
}
//墙体
public void setWall(String wall){
this.buildingComponents.add(wall);
}
//屋顶
public void setRoof(String roof){
this.buildingComponents.add(roof);
}
@Override
public String toString() {
StringBuilder buildingStr = new StringBuilder();
for (int i = buildingComponents.size()-1;i>=0;i--){
buildingStr.append(buildingComponents.get(i));
}
return buildingStr.toString();
}
}
这个建筑物类的内部构造看起来稍微有点复杂(实际应用中会更复杂),怎样才能用这个复杂的类构建出一个房子对象呢?首先应该调用哪个建造方法才能保证正确的建造工序,而不至于屋顶在下面,地基却跑到天上去呢?地基、墙体、屋顶,这些组件都去哪里找,如何建造?地产开发商(客户端)感到十分困惑,一头雾水。
建筑施工方
组建专业的建筑施工团队对建筑工程项目的实施至关重要,于是地产开发商决定通过招标的方式来选择施工方。招标大会上有很多建筑公司来投标,他们各有各的房屋建造资质,有的能建别墅,有的能建多层公寓,还有能力更强的能建摩天大楼,建造工艺也各有区别。但无论如何,开发商规定施工方都应该至少具备三大组件的建造能力,于是施工标准公布出来了
package Builder;
public interface Builder {
void buildBasement();
void buildWall();
void buildRoof();
Building getBuilding();
}
施工方接口规定了3个施工标准,它们分别对应建造地基、建造墙体以及建造屋顶,另外,还定义了一个获取建筑物的接口getBuilding(),以供产品的交付。接着,开发商按此标准启动了招标工作,一个别墅施工方中标。
别墅施工方类HouseBuilder
package Builder;
public class HouseBuilder implements Builder {
private Building house;
public HouseBuilder(){
house = new Building();
}
@Override
public void buildBasement() {
System.out.println("挖土方、部署管道、线缆、水泥加固,搭建围墙、花园。");
house.setBasement("+++++++++++\n");
}
@Override
public void buildWall() {
System.out.println("搭建木制框架,石膏板封墙并粉饰内外墙。");
house.setWall("|田|田田| \n");
}
@Override
public void buildRoof() {
System.out.println("建造屋顶、阁楼、安装烟冲,做好防水。");
house.setRoof("/||||||||\n");
}
@Override
public Building getBuilding() {
return house;
}
}
这个别墅施工方看起来具备很高的施工水平,对别墅的建造工艺看起来十分考究。不管是建造地基、建造墙体,还是建造屋顶(第22行),别墅施工方都能做到,完全符合开发商公布的施工标准。接下来开发商又考察了一个多层公寓施工方
多层公寓施工方类ApartmentBuilder
package Builder;
public class ApartmentBuilder implements Builder {
private Building apartment;
public ApartmentBuilder() {
apartment = new Building();
}
@Override
public void buildBasement() {
System.out.println("深挖地基,修建地下车库,部署管道、线缆、风道。");
apartment.setBasement("||_________________________||\n");
}
@Override
public void buildWall() {
System.out.println("搭建多次建筑架构,建造电梯井,钢筋混泥土浇灌。");
for (int i = 0;i <8;i++){//此处假设固定八层
apartment.setWall("|| 口 口 口||\n");
}
}
@Override
public void buildRoof() {
System.out.println("封顶、部署通风井,做防水层,保温层");
apartment.setRoof("||=============||\n");
}
@Override
public Building getBuilding() {
return apartment;
}
}
多层公寓施工方成功中标,它同别墅施工方一样符合开发商公布的施工标准,但施工方法实现上大相径庭,例如建造地基方法实现buildBasement()中地基挖得比较扎实,以及建造墙体方法buildWall()中进行的迭代施工,这里建造的应该是一梯四户(4个窗户)的8层(循环8次)公寓楼,其建造工艺与别墅施工方有很大不同。
工程总监
虽然施工方很好地保证了建筑物三大组件的施工质量,但开发商还是不放心,因为施工方毕竟只负责干活,施工流程无法得到控制。为了解决这个问题,开发商又招聘了一个专业的工程总监来做监理工作,他亲临施工现场指导施工,并把控整个施工流程
工程总监类Director
package Builder;
public class Director {
private Builder builder;
public Director(Builder builder) {
this.builder = builder;
}
public void setBuilder(Builder builder){
this.builder = builder;
}
public Building direct(){
System.out.println("项目启动。。。。。");
//1,打好地基
builder.buildBasement();
//2,建造框架、墙体
builder.buildWall();
//3,封顶
builder.buildRoof();
System.out.println("施工竣工。。。。。。。。。。。。");
return builder.getBuilding();
}
}
工程总监的角色就像电影制作中的导演一样,他从宏观上管理项目并指导整个施工队的建造流程。在代码的指导方法中,我们依次调用施工方的打地基方法buildBasement()、建造墙体方法buildWall()及建筑物封顶方法buildRoof(),保证了建筑物自下而上的建造工序。可以看到,施工方是在由外部注入的,所以工程总监并不关心是哪个施工方来造房子,更不关心施工方有什么样的建造工艺,但他能保证对施工工序的绝对把控,也就是说,工程总监只控制施工流程。
这里我们对工程总监direct的指导方法进行了简化,实际应用中的建造流程也许会更加复杂,且组装各个组件的流程有相对固定的逻辑,所以可以从施工方的建造方法中抽离出来并固化在director类中。
项目实施
至此招标工作结束,一切准备就绪,所有项目干系人(施工方、工程总监)都已就位,可以开始组建项目团队并启动项目了。我们来看开发商如何拿到产品
package Builder;
public class Client {
public static void main(String[] args) {
Director director = new Director(new HouseBuilder());
System.out.println(director.direct());
//替换施工队,建造公寓
director.setBuilder(new ApartmentBuilder());
System.out.println(director.direct());
}
}
Go版本
Building.go
package builder
import (
"strings"
)
type Building struct {
buildingComponents []string
}
func (b *Building) setBasement(basement string) {
b.buildingComponents = append(b.buildingComponents, basement)
}
func (b *Building) setWall(wall string) {
b.buildingComponents = append(b.buildingComponents, wall)
}
func (b *Building) setRoof(roof string) {
b.buildingComponents = append(b.buildingComponents, roof)
}
func (b *Building) String() string {
var bt strings.Builder //gO官方推荐使用这种方式拼接
for i := 0; i < len(b.buildingComponents); i++ {
bt.WriteString(b.buildingComponents[i])
}
return bt.String()
}
Builder.go
package builder
import "fmt"
type Builder interface {
BuildBasement()
BuildWall()
BuildRoof()
GetBuilding() Building
}
type HouseBuilder struct {
house Building
}
func NewHouseBuilder() *HouseBuilder {
return &HouseBuilder{
house: Building{
buildingComponents: make([]string, 0),
},
}
}
func (h *HouseBuilder) BuildBasement() {
fmt.Println("挖土方、部署管道、线缆、水泥加固,搭建围墙、花园。")
h.house.setBasement("+++++++++++\n")
}
func (h *HouseBuilder) BuildWall() {
fmt.Println("搭建木制框架,石膏板封墙并粉饰内外墙。")
h.house.setWall("|田|田田| \n")
}
func (h *HouseBuilder) BuildRoof() {
fmt.Println("建造屋顶、阁楼、安装烟冲,做好防水。")
h.house.setRoof("/||||||||\n")
}
func (h *HouseBuilder) GetBuilding() Building {
return h.house
}
type ApartmentBuilder struct {
apartment Building
}
func NewApartmentBuilder() *ApartmentBuilder {
return &ApartmentBuilder{
apartment: Building{
buildingComponents: make([]string, 0),
},
}
}
func (a *ApartmentBuilder) BuildBasement() {
fmt.Println("深挖地基,修建地下车库,部署管道、线缆、风道。")
a.apartment.setBasement("||_________________________||\n")
}
func (a *ApartmentBuilder) BuildWall() {
fmt.Println("搭建多次建筑架构,建造电梯井,钢筋混泥土浇灌。")
for i := 0; i < 8; i++ {
a.apartment.setWall("|| 口 口 口||\n")
}
}
func (a *ApartmentBuilder) BuildRoof() {
fmt.Println("封顶、部署通风井,做防水层,保温层")
a.apartment.setRoof("||=============||\n")
}
func (a *ApartmentBuilder) GetBuilding() Building {
return a.apartment
}
Director.go
package builder
import "fmt"
type Director struct {
Builder
}
func NewDirector(builder Builder) *Director {
return &Director{
Builder: builder,
}
}
func (d *Director) SetDirector(builder Builder) {
d.Builder = builder
}
func (d *Director) Direct() Building {
fmt.Println("项目启动。。。。。")
d.Builder.BuildBasement()
d.BuildWall()
d.BuildRoof()
fmt.Println("施工竣工。。。。。")
return d.GetBuilding()
}
main.go
package main
import (
builder "desginPatterns/Builder"
"fmt"
)
func main() {
var director *builder.Director = builder.NewDirector(builder.NewHouseBuilder())
fmt.Println(director.Direct())
director.SetDirector(builder.NewApartmentBuilder())
fmt.Println(director.Direct())
}
工艺与工序
项目团队将建筑物产品交付给开发商,项目终于顺利竣工。施工方接口对施工标准的抽象化、标准化使建造者(施工方)的建造质量达到既定要求,且使各建造者的建造“工艺”能够个性化、多态化。此外,工程总监将工作流程抽离出来独立于建造者,使建造“工序”得到统一把控。最终,各种建筑产品都得到了业主的认可,成功离不开团队的共同协作与努力,请参看建造者模式的类结构
Product(产品):复杂的产品类,构建过程相对复杂,需要其他组件组装而成。对应本章例程中的建筑物类。
Builder(建造者):建造者接口,定义了构成产品的各个组件的构建标准,通常有多个步骤。对应本章例程中的施工方接口。
ConcreteBuilder(建造者实现):具体的建造者实现类,可以有多种实现,负责产品的组装但不包含整体建造逻辑。对应本章例程中的别墅施工方类与多层公寓施工方类。
Director(指导者):持有建造者接口引用的指导者类,指导建造者按一定的逻辑进行建造。对应本章例程中的工程总监类。
复杂对象的构建显然需要专业的建造团队,建造标准的确立让产品趋向多样化,其建造工艺可以交给多位建造者去各显其长,而建造工序则交由工程总监去全局把控,把“变”与“不变”分开,使“工艺多样化”“工序标准化”,最终实现通过相同的构建过程生产出不同产品,这也是建造者模式要达成的目标。