GOF中各种构造模式的目的都是获取最终产品。在Terran章节中介绍了三种工厂模式的关注点仅仅是最终产品;而本章所介绍的建造者模式,关注的除了产品之外,它还关心着产品构造的细节,这一点是Builder模式和Factory模式最显著的区别。
在上一章中,我们曾经说过“使用者通常是不关心产品具体成员的创建过程的,因此也不应当把各个产品的创建过程揉合到具体逻辑之中”。大多数情况确实是这样的,但是可以想象一些特殊情况:譬如你使用Builder模式去调配一杯鸡尾酒,应当希望可以调整内部各种掺酒的比例;又或者你使用Builder模式去装配一部汽车,不幸汽车出厂时发现质量有问题,你肯定希望能快速的诊断到底是装配的时候发动机出了问题,还是装配的轮胎或者挡风玻璃有问题。
目的:
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
场景:
Sarah Kerrigan(剧情中Zerg的刀锋女王)的部落现在还弱小,而且正在受到Jim Raynor的持续压制,为了能扭转不利的局面,Kerrigan升级了Overlord的速度与运输技能,很明显,Kerrigan要空投了。
根据当前的科技状况,Kerrigan能掌握的部队有:
Overlord:领主,现在主要充当运输机的角色。
Zergling:小狗,Zerg的初级近战兵种。
Hydralisk:刺蛇,Zerg的初级远程兵种。
Lurker:潜伏者,Zerg的中级兵种,擅长范围攻击。
分析:
Kerrigan的空投策略是多点小规模同时骚扰,这需要多组空投部队(最终产品),每组空投部队都由一个Overload装在着,外表看起来是一样的,执行的行为也都是攻击、破坏、骚扰;但是我们可以根据战场实际情况,调整装配进各个Overload中的部队的细节,获得更好的效果。
当前为了演示需要,制定了SceneA和SceneB两种场景说适合的兵力配置,先看看以下UML图和代码:
下面是一个Zerg战斗部队(最终产品)的代码,这支部队各个兵种的数量是可调整的:
/** * Builder的产品,代表一组骚扰部队 */ public class ZergProduct { private int overload; private int zergling; private int hydralisk; private int lurker; public int getOverload() { return overload; } public void setOverload(int overload) { this.overload = overload; } public int getZergling() { return zergling; } public void setZergling(int zergling) { this.zergling = zergling; } public int getHydralisk() { return hydralisk; } public void setHydralisk(int hydralisk) { this.hydralisk = hydralisk; } public int getLurker() { return lurker; } public void setLurker(int lurker) { this.lurker = lurker; } public String toString() { return "当前部队包括:" + overload + "个Overload; " + zergling + "个Zergling; " + hydralisk + "个Hydralisk; " + lurker + "个Lurker"; } }
|
接着,我们建立了2中不同的场景使用不同的空投部队去应对:
场景A空投了1个Overload; 2个Zergling; 1个Hydralisk; 1个Lurker
场景B空投了1个Overload; 4个Hydralisk
有一点需要说明一下,在实际情况下,两种产品的不同一般不会仅仅体现在产品组成部分的数量变化上,事实上如果仅仅是数量的不一致,那可以把这种区别看作是装配期间才需要关心的职责,放入到Director中完成。这里的A、B场景建造者只保留数量上的区别主要是为了简易起见,实际情况下我们可以在这里处理更本质的差异,譬如除了放入不同Overload中Zergling个数不一样外,还可以根据不同情况将“吃粗粮长大的Zergling”、“吃奶粉长大的Zergling”放入不同的Overload中。
建立这2种建造者的实现代码如下:
/** * 适应场景A的装配器 */ public class SceneABuilder implements ArmyBuilder { ZergProduct product = new ZergProduct(); public void buildHydralisk() { product.setHydralisk(1); } public void buildLurker() { product.setLurker(1); } public void buildOberload() { product.setOverload(1); } public void buildZergling() { product.setZergling(2); } public ZergProduct getResult() { ZergProduct finlishProduct = product; product = new ZergProduct(); return finlishProduct; } } /** * 适应场景B的装配器 */ public class SceneBBuilder implements ArmyBuilder { ZergProduct product = new ZergProduct(); public void buildHydralisk() { product.setHydralisk(4); } public void buildLurker() { product.setLurker(0); } public void buildOberload() { product.setOverload(0); } public void buildZergling() { product.setZergling(0); } public ZergProduct getResult() { ZergProduct finlishProduct = product; product = new ZergProduct(); return finlishProduct; } } |
同时,我们将具体的建造过程放到Director里面以便分离职责。这样当日后部队建造顺序需要调整,譬如要求先建造Zergling,再建立Hydralisk我们仅需要调整Director的内容。又或者产品的某一部分不需要或者不可用的时候,譬如当虫族Spawning Pool(血池,制造Zergling的必须建筑)被破坏之后,我们要达到不能再使用Zergling这种兵种的效果,就不需要修改每个具体的建造者,只需要在Director中把builder.buildZergling();这句去掉就可以生产出来的产品不再包含Zergling。
Director的实现代码如下:
/** * 描述建造者的建造过程 */ public class Director { public void construct(ArmyBuilder builder) { builder.buildOberload(); builder.buildHydralisk(); builder.buildZergling(); builder.buildLurker(); } } |
然后再看一下Builder模式是如何被使用的:
/** * 模拟建造两只部队 */ public class WarField { public static void main(String[] args) { Director director = new Director(); ArmyBuilder builderA = new SceneABuilder(); ArmyBuilder builderB = new SceneBBuilder(); director.construct(builderA); director.construct(builderB); System.out.println(builderA.getResult()); System.out.println(builderB.getResult()); } } |
从上面的过程可以看到,Builder模式与Abstract Factory模式从目的上讲是相当类似的,都是为了生产出复杂产品(两个例子的产品都是一组多兵种组合的协同小组),他们的主要区别就是Abstract Factory模式的建造过程完全封装在工厂之内,使用时调用工厂立刻获取到产品。而Builder模式将生产过程放在Director之中,可以精细控制最终产品的生产过程,使用的时候是一步一步的得到最终产品。
最后,展示一下程序的最终运行结果:
总结:
所谓设计模式就是人们总结出来,可以在特定上下文环境下解决一些常见问题的最佳实践。六种构造模式的目的都是生产产品,就像无论在Terran还是Zerg,使用的是何种方法都是需要生产部队才能赢得比赛,但只有根据种族的特点,采取最佳的营运与配搭,才能获得快速的发展。使用模式也一样,在实际编写代码时,我们是为了解决上下文遇到的问题而去使用模式,而不要脱离上下文、脱离具体问题为了使用模式而使用模式。
我看来,打星际争霸学会几种开局不难,难的是应变。把每种模式的框架背下来写代码中套用上也不难,真正掌握了模式的标准应当是使用的时候能否做到“顺势而动、行云流水”。
从本章开始介绍的三种Factory模式以外的构造模式,也让Zerg的发展正式走进了我们的剧情之中,现在让我们一起看看Zerg中一些很神奇部队生产方式,去学习下一种构造模式。