11.1 变化是永恒的




图11-1 汽车模型类图

类图比较简单,在CarModel中我们定义了一个setSequence方法,车辆模型的这几个动作要如何排布,是在这个ArrayList中定义的,然后run()方法根据sequence定义的顺序完成指定的顺序动作,与我们上一章节介绍的模板方法模式是不是非常类似?好,我们先看CarModel源代码,如代码清单11-1所示。

代码清单11-1 车辆模型的抽象类


public abstract class CarModel {

//这个参数是各个基本方法执行的顺序

private ArrayList<String> sequence = new ArrayList<String>();

//模型是启动开始跑了

protected abstract void start();

//能发动,那还要能停下来,那才是真本事

protected abstract void stop();

//喇叭会出声音,是滴滴叫,还是哔哔叫

protected abstract void alarm();

//引擎会轰隆隆地响,不响那是假的

protected abstract void engineBoom();

//那模型应该会跑吧,别管是人推的,还是电力驱动,总之要会跑

final public void run() {

//循环一边,谁在前,就先执行谁

for(int i=0;i<this.sequence.size();i++){

String actionName = this.sequence.get(i);

if(actionName.equalsIgnoreCase("start")){

this.start(); //开启汽车

}else if(actionName.equalsIgnoreCase("stop")){

this.stop(); //停止汽车

}else if(actionName.equalsIgnoreCase("alarm")){

this.alarm(); //喇叭开始叫了

}else if(actionName.equalsIgnoreCase("engine boom")){ //如果是engine boom关键字

this.engineBoom(); //引擎开始轰鸣

}

}

}

//把传递过来的值传递到类内

final public void setSequence(ArrayList<String> sequence){

this.sequence = sequence;

}

CarModel的设计原理是这样的,setSequence方法是允许客户自己设置一个顺序,是要先启动响一下喇叭再跑起来,还是要先响一下喇叭再启动,对于一个具体的模型永远都固定的,但是对N多个模型就是动态的了。在子类中实现父类的基本方法,run()方法读取sequence,然后遍历sequence中的字符串,哪个字符串在先,就先执行哪个方法。

两个实现类分别实现父类的基本方法,奔驰模型如代码清单11-2所示。

代码清单11-2 奔驰模型代码



public class BenzModel extends CarModel {

protected void alarm() {

System.out.println("奔驰车的喇叭声音是这个样子的...");

}

protected void engineBoom() {

System.out.println("奔驰车的引擎室这个声音的...");

}

protected void start() {

System.out.println("奔驰车跑起来是这个样子的...");

}

protected void stop() {

System.out.println("奔驰车应该这样停车...");

}

}

宝马车模型如代码清单11-3所示。

代码清单11-3 宝马模型代码



public class BMWModel extends CarModel {

protected void alarm() {

System.out.println("宝马车的喇叭声音是这个样子的...");

}

protected void engineBoom() {

System.out.println("宝马车的引擎室这个声音的...");

}

protected void start() {

System.out.println("宝马车跑起来是这个样子的...");

}

protected void stop() {

System.out.println("宝马车应该这样停车...");

}

}



图11-2 增加了建造者的汽车模型类图

增加了一个CarBuilder抽象类,由它来组装各个车模,要什么类型什么顺序的车辆模型,都由相关的子类完成,首先编写CarBuilder代码,如代码清单11-5所示。

代码清单11-5 抽象汽车组装者




public abstract class CarBuilder {

//建造一个模型,你要给我一个顺序要,就是组装顺序

public abstract void setSequence(ArrayList<String> sequence);

//设置完毕顺序后,就可以直接拿到这个车辆模型

public abstract CarModel getCarModel();

}

很简单,每个车辆模型都要有确定的运行顺序,然后才能返回一个车辆模型。奔驰车的组装者如代码清单11-6所示。

代码清单11-6 奔驰车组装者



public class BenzBuilder extends CarBuilder {

private BenzModel benz = new BenzModel();

public CarModel getCarModel() {

return this.benz;

}

public void setSequence(ArrayList<String> sequence) {

this.benz.setSequence(sequence);

}

}

非常简单实用的程序,给定一个汽车的运行顺序,然后就返回一个奔驰车,简单了很多,宝马车的组装与此相同,如代码清单11-7所示。

代码清单11-7 宝马车组装者



public class BMWBuilder extends CarBuilder {

private BMWModel bmw = new BMWModel();

public CarModel getCarModel() {

return this.bmw;

}

public void setSequence(ArrayList<String> sequence) {

this.bmw.setSequence(sequence);

}

}

}

图11-3 完整汽车模型类图

类图看着复杂了,但是还是比较简单,我们增加了一个Director类,负责按照指定的顺序生产模型,其中方法说明如下:

getABenzModel方法

组建出A型号的奔驰车辆模型,其过程为只有启动(start)、停止(stop)方法,其他的引擎声音、喇叭都没有。

getBBenzModel方法

组建出B型号的奔驰车,其过程为先发动引擎(engine boom),然后启动(star),再然后停车(stop),没有喇叭。

getCBMWModel方法

组建出C型号的宝马车,其过程为先喇叭叫一下(alarm),然后(start),再然后是停车(stop),引擎不轰鸣。

getDBMWModel方法

组建出D型号的宝马车,其过程就一个启动(start),然后一路跑到黑,永动机,没有停止方法,没有喇叭,没有引擎轰鸣。

其他的E型号、F型号……等等,可以有很多,启动(start)、停止(stop)、喇叭(alarm)、引擎轰鸣(engine boom)这四个方法在这个类中可以随意的自由组合,有几种呢?好像是排列组合,这个不会算,高中数学没学好,反正有很多种了,都可以实现。Director类如代码清单11-10所示。

代码清单11-10 导演类



public class Director {

private ArrayList<String> sequence = new ArrayList();

private BenzBuilder benzBuilder = new BenzBuilder();

private BMWBuilder bmwBuilder = new BMWBuilder();

/*

* A类型的奔驰车模型,先start,然后stop,其他什么引擎了,喇叭一概没有

*/

public BenzModel getABenzModel(){

//清理场景,这里是一些初级程序员不注意的地方

this.sequence.clear();

//这只ABenzModel的执行顺序

this.sequence.add("start");

this.sequence.add("stop");

//按照顺序返回一个奔驰车

this.benzBuilder.setSequence(this.sequence);

return (BenzModel)this.benzBuilder.getCarModel();

}

/*

* B型号的奔驰车模型,是先发动引擎,然后启动,然后停止,没有喇叭

*/

public BenzModel getBBenzModel(){

this.sequence.clear();

this.sequence.add("engine boom");

this.sequence.add("start");

this.sequence.add("stop");

this.benzBuilder.setSequence(this.sequence);

return (BenzModel)this.benzBuilder.getCarModel();

}

/*

* C型号的宝马车是先按下喇叭(炫耀嘛),然后启动,然后停止

*/

public BMWModel getCBMWModel(){

this.sequence.clear();

this.sequence.add("alarm");

this.sequence.add("start");

this.sequence.add("stop");

this.bmwBuilder.setSequence(this.sequence);

return (BMWModel)this.bmwBuilder.getCarModel();

}

/*

* D类型的宝马车只有一个功能,就是跑,启动起来就跑,永远不停止,牛叉

*/

public BMWModel getDBMWModel(){

this.sequence.clear();

this.sequence.add("start");

this.bmwBuilder.setSequence(this.sequence);

return (BMWModel)this.benzBuilder.getCarModel();

}

/*

* 这里还可以有很多方法,你可以先停止,然后再启动,或者一直停着不动,静态的嘛

* 导演类嘛,按照什么顺序是导演说了算

*/

}

顺便说一下,大家看一下程序中有很多this调用,这个我一般是这样要求项目组成员的,如果你要调用类中的成员变量或方法,需要在前面加上this关键字,不加也能正常的跑起来,但是不清晰,加上this关键字,我就是要调用本类中成员变量或方法,而不是本方法的中的一个变量,还有super方法也是一样,是调用父类的的成员变量或者方法,那就加上这个关键字,不要省略,这要靠约束,还有就是程序员的自觉性,他要是死不悔改,那咱也没招。

注意 上面每个方法都一个this.sequence.clear(),这个估计你一看就明白,但是作为一个系统分析师或是技术经理一定要告诉告诉项目成员,ArrayList和HashMap如果定义成类的成员变量,那你在方法中调用一定要做一个clear的动作,防止数据混乱。如果你发生过一次类似问题的话,比如ArrayList中出现一个“出乎意料”的数据,而你又花费了几个通宵才解决这个问题,那你会有很深刻的印象。

有了这样一个导演类后,我们的场景类就更容易处理了,牛叉公司要A类型的奔驰车1W辆,B类型的奔驰车100W辆,C类型的宝马车1000W辆,D类型的不需要,非常容易处理,如代码清单11-11所示。

代码清单11-11 导演类



public class Client {

public static void main(String[] args) {

Director director = new Director();

//1W辆A类型的奔驰车

for(int i=0;i<10000;i++){

director.getABenzModel().run();

}

//100W辆B类型的奔驰车

for(int i=0;i<1000000;i++){

director.getBBenzModel().run();

}

//1000W辆C类型的宝马车

for(int i=0;i<10000000;i++){

director.getCBMWModel().run();

}

}

}

清晰,简单吧,我们写程序重构的最终目的就是:简单、清晰,代码是让人看的,不是写完就完事了,我一直在教育我带的团队,Java程序不是像我们前辈写二进制代码、汇编一样,写完基本上就自己能看懂,别人看就跟看天书一样,现在的高级语言,要像写中文汉字一样,你写的,别人能看懂。——这就是建造者模式。