手工时代的中国工匠相信愿力无边,不管是做佛像,还是打家具。即使只是打造一个金丝楠木柜子,可能都不是一个工匠一生就能做完的。往往是爷爷做出粗坯,父亲做完粗工,孙子再精雕细琢,穷尽三代才打造出一件精湛的柜子。陆续建造了一千六百年的莫高窟,那是多少代无名工匠,用尽了自己的体温去焐热了菩萨的慈悲。
——于丹《人间有味是清欢》
将一个复杂对象的构建与他的表示分离,使得同样的构建过程可以创建不同的表示
不止有 Builder 和 ConcreteBuilder 是生成器模式的构成,
Director(导演)
也是生成器模式不可或缺的一部分,因为这部分是生成器用于保证自己交付给 client 的产品是一个完整对象的关键而且 client 会同时和 Director 和 Builder 两个部分打交道
这次的例子会有点长,先做个深呼吸
准备好了?那我们开始了:
假设我们要用代码来表示 一家玩具厂的玩具车产线,这种玩具车具有如下要求:
这种玩具车还分三种型号,就像这样:
毋庸置疑,我们需要创建出一个 玩具车 的实体类ToyCar
,就像这样:
你当然可以在 ToyCar 里直接新增三种不同的构造方法来实现这个效果,就像这样:
public class ToyCar{
……
public static ToyCar createModelA(){
ToyCar car = new ToyCar();
car.底盘 = new 底盘();
car.车轮 = new α车轮();
car.遥控器 = new 甲型号遥控器();
car.车顶灯 = null;
car.喇叭 = new 音乐喇叭();
return car;
}
public static ToyCar createModelB(){
ToyCar car = new ToyCar();
car.底盘 = new 底盘();
car.车轮 = new β车轮();
car.遥控器 = new 乙型号遥控器();
car.车顶灯 = new 彩色车灯();
car.喇叭 = null;
return car;
}
public static ToyCar createModelC(){
ToyCar car = new ToyCar();
car.底盘 = new 底盘();
car.车轮 = new β车轮();
car.遥控器 = new 甲型号遥控器();
car.车顶灯 = new 彩色车灯();
car.喇叭 = new 音乐喇叭();
return car;
}
……
}
如果这家玩具厂 永远都不开新品产线并且每种类型的玩具车的构成都不发生改变,那么这个方案非常完美。虽然他在静态工厂里面暴露了玩具车的具体构成(这让 ToyCar 和 各个组件 之间形成了紧耦合),但是如果不变动的话,问题并不大
可是设计模式就是考虑变化的情况,如果没有变化,那全都用硬编码不就好了?
很显然,如果我们加入了新的型号,我们需要新增新的静态工厂方法。最重要的是,这些工厂方法所产出的对象之间并没有什么本质不同,他们只是状态迥异的 “同个对象” 而已
这和工厂方法的理念是背道而驰的,我们采用工厂方法的时候,通常是每个工厂方法所产出的产品是同个产品根类的不同方向拓展
另两种实现方式:
- 创建一个工厂类用于存放上面的工厂方法
- 定义一个接收所有组件作为参数的构造方法
这两种方法的本质和静态工厂方法实现是一样的,只不过这两种方法解除了 ToyCar 和具体组件类之间的紧耦合
而前者把这部分硬编码放到了工厂类中,后者把硬编码放到了调用构造方法的上下文中
这种方案的意思是说,这样写:
public interface ToyCarComponentFactory{
//创建一个底盘
Chassis createChassis();
//创建一个轮胎
Tyre createTyre();
//创建一个遥控器
RemoteControl createRemoteControl();
//创建一个车顶灯
RoofLigh createRoofLight();
//创建一个喇叭
Trumpet createTrumpet();
}
//型号A的组件工厂
public class ModelAComponentFactory implements ToyCarComponentFactory{
//创建一个底盘
public Chassis createChassis(){
return 底盘对象;
}
//创建一个轮胎
public Tyre createTyre(){
return α轮胎对象;
}
//创建一个遥控器
public RemoteControl createRemoteControl(){
return 甲型号遥控器;
}
//创建一个车顶灯
public RoofLigh createRoofLight(){
return null;
}
//创建一个喇叭
public Trumpet createTrumpet(){
return 音乐喇叭对象;
}
}
//型号B的组件工厂
public class ModelBComponentFactory implements ToyCarComponentFactory{
//创建一个底盘
public Chassis createChassis(){
return 底盘对象;
}
//创建一个轮胎
public Tyre createTyre(){
return β轮胎对象;
}
//创建一个遥控器
public RemoteControl createRemoteControl(){
return 乙型号遥控器对象;
}
//创建一个车顶灯
public RoofLigh createRoofLight(){
return 彩色车灯对象;
}
//创建一个喇叭
public Trumpet createTrumpet(){
return null;
}
}
//型号C的组件工厂
public class ModelCComponentFactory implements ToyCarComponentFactory{
//创建一个底盘
public Chassis createChassis(){
return 底盘对象;
}
//创建一个轮胎
public Tyre createTyre(){
return β轮胎对象;
}
//创建一个遥控器
public RemoteControl createRemoteControl(){
return 甲型号遥控器对象;
}
//创建一个车顶灯
public RoofLigh createRoofLight(){
return 彩色车灯对象;
}
//创建一个喇叭
public Trumpet createTrumpet(){
return 音乐喇叭对象;
}
}
这种写法的问题同样很大,抽象工厂确实帮我们解决了 解耦和重复修改 的问题,但是他将 ToyCar 对象的 组装权
交给了 client,谁知道 client 会组装出一个什么样的玩具车?也许会是一个没有底盘光有喇叭的小号;又或者是只有底盘没有轮胎的手电筒。
ToyCar 是一个由多个组件构成的比较复杂的对象,你必须要保证你交付给 client 的时候不会是一个构造到一半的对象。所以,抽象工厂也被pass了
现在我们推翻前面的设计,把 Line(生产线)
也用一个类来表示,然后在 生产线 中定义一个 Buidler(生成器)
,让这个Builder来生成 ToyCar,就像这样:
//玩具车生产线
public class ToyCarLine{
private ToyCarBuilder builder;//玩具车生成器
public ToyCarLine(ToyCarBuilder builder){
this.builder = builder;
}
public void changeToyCarBuilder(ToyCarBuilder builder){
this.builder = builder;
}
public ToyCar create(){
builder.reset();
builder.buildChassis();
builder.buildTyre();
builder.buildRemoteControl();
builder.buildRoofLight();
builder.buildTrumpet();
return builder.getResult();
}
}
//玩具车生成器
public abstract class ToyCarBuilder{
protected ToyCar result;
public void reset(){
result = new ToyCar();
}
public abstract void buildChassis();
public abstract void buildTyre();
public abstract void buildRemoteControl();
public abstract void buildRoofLight();
public abstract void buildTrumpet();
public ToyCar getResult(){
return result;
}
}
public class ModelABuilder extends ToyCarBuilder{
public void buildChassis(){
result.底盘 = new 底盘();
}
public void buildTyre(){
result.轮胎 = new α轮胎();
}
public void buildRemoteControl(){
result.遥控器 = new 甲类型遥控器();
}
public void buildRoofLight(){
result.车顶灯 = null;
}
public void buildTrumpet(){
result.喇叭 = new 音乐喇叭();
}
}
public class ModelBBuilder extends ToyCarBuilder{
public void buildChassis(){
result.底盘 = 底盘;
}
public void buildTyre(){
result.轮胎 = new β轮胎();
}
public void buildRemoteControl(){
result.遥控器 = new 乙类型遥控器();
}
public void buildRoofLight(){
result.车顶灯 = new 彩色车灯();
}
public void buildTrumpet(){
result.喇叭 = null;
}
}
public class ModelCBuilder extends ToyCarBuilder{
public void buildChassis(){
result.底盘 = 底盘;
}
public void buildTyre(){
result.轮胎 = new β轮胎();
}
public void buildRemoteControl(){
result.遥控器 = new 甲类型遥控器();
}
public void buildRoofLight(){
result.车顶灯 = new 彩色车灯();
}
public void buildTrumpet(){
result.喇叭 = new 音乐喇叭;
}
}
我们通过 ToyCarLine 实现了对 ToyCar 产出时的控制,每个构筑步骤一定会被调用,无论他有没有对成品做操作
当玩具车要开新的产线的时候,我只需要新增新的 ToyCarBuilder 子类就可以了,ToyCarLine 和 client 中不需要任何修改
如果玩具车的 “生产线” 生产到一半想要切换成品的类型,则直接切换 ToyCarLine 的 builder 引用就可以实现
也就是说,在生成器模式下,我们是这样生产一部 ToyCar 的:
对照生成器的UML的话,则有:
这是一个标准的生成器实现
看起来和抽象工厂很像是吧,但是他们有两个本质区别
抽象工厂无法控制 ToyCar 构筑的流程,而生成者由于有 Director 的存在,可以保证从他这里产出的对象都是到手可用的,没有状态问题的
抽象工厂所产出的内容,通常都是在一个大的框架下的不同组件;
而生成器的关注点是给一个成品内添加不同的组件
从我第一次接触 生成器模式 开始,就觉得他特别像工厂里的流水线
流水线上每个人负责不同的工位(Builder里的每个方法都负责不同的部分),但都对相同的一个部品进行加工(都对result进行加工)。
这就和 Director 对 Product 做的事是一模一样,Builder 会定义加工产品的这条流水线一共有多少个工位,至于每个工位具体做什么事情,由实现 Builder 的具体生成器决定,但是 Builder 要保证所有工位加工完之后就可以拿到一个成品
在 玩具车 的例子中,有两个方法是给产品里的属性赋空值,这是为了配合前面使用工厂模式的例子所以才这样写的(因为工厂方法必须要有返回值,那怕是null)
事实上由于生成器是一种加工的模式,所以他是允许某个工位对产品不做任何操作的
所以 ModelABuilder中的buildRoofLight 和 ModelBBuilder中的buildTrumpet 是可以什么都不写的,这样也不会影响 Director 对生成器的调用
Director 中通过对 Builder 的切换,最终实现了相同的行为在不同状态下有不同的产品产出。这时候如果你将 Builder 类簇看成是一个策略类簇的话就会发现,其实 Director 是一个策略模式实现
这并不矛盾,没有人规定一种设计模式里面只能用当前这种设计模式里面的内容,抽象工厂里还有工厂方法,工厂方法的例子里我还用到享元和单例呢
设计模式就像是武侠小说里的武功秘籍一样,他记载的只是招式,招式和招式之间也会存在相生相克和相互关联的时候
所以我们要像张无忌那样,先记住这些招式,再把这些招式全忘掉
当你发现你在忘掉所有招式的情况下,还能把他们融入自己的设计中,那应该就是大成了
就像上文的例子那样,并不是因为老师说用这个更好,所以我用这个。而是因为我们发现这样写好像更合理,更易于拓展,自然而然的写出了这样的代码。而恰巧这种手法和前人已经总结过的生成器模式一致,仅此而已
请不要总把他当作解决某种问题的方法,让他成为你思考问题的方式