-
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程序不是像我们前辈写二进制代码、汇编一样,写完基本上就自己能看懂,别人看就跟看天书一样,现在的高级语言,要像写中文汉字一样,你写的,别人能看懂。——这就是建造者模式。