建造者模式【Builder Pattern 】
在模板方法模式时用到了公司生产马车的例子。这里继续,客户有了新的要求,就是能够控制基本方法的执行顺序。
先看一下类图:
定义一个车辆模型的抽象类,所有的车辆模型都继承这个类
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")){ //如果是start关键字,
this.start(); //开启汽车
}else if(actionName.equalsIgnoreCase("stop")){ //如果是stop关键字
this.stop(); //停止汽车
}else if(actionName.equalsIgnoreCase("alarm")){ //如果是alarm关键字
this.alarm(); //喇叭开始叫了
}else if(actionName.equalsIgnoreCase("engine boom")){ //如果是engine
boom关键字
this.engineBoom(); //引擎开始轰鸣
}
}
//把传递过来的值传递到类内
final public void setSequence(ArrayList<String> sequence){
this.sequence = sequence;
}
}
然后定义要生产的奔驰、宝马类。
最后在主类设置执行的顺序,进行调用:
BenzModel benz = new BenzModel();
ArrayList<String> sequence = new ArrayList<String>(); //存放run的顺序
sequence.add("engine boom"); //客户要求,run的时候时候先发动引擎
sequence.add("start"); //启动起来
sequence.add("stop"); //开了一段就停下来
//然后我们把这个顺序给奔驰车:
benz.setSequence(sequence);
benz.run();
其中 setSequence 方法是允许客户自己设置一个顺序,是要先跑起来在有引擎声音还是先有引擎声音再跑起来,还是说那个喇叭就不要响,对于一个具体的模型永远都固定的。这样的设计体现不出程序的扩展性。
接下来客户可能提各种各种的需求,不同种类的车型可以有不同的执行顺序,有的可以不执行。于是乎,先看一下下面的类图设计:
增加了一个 CarBuilder 的抽象类,以及两个实现类,其目的是你要什么排列顺序的车,我就给你什么顺序的车,那我们先看 CarBuilder的定义:
public abstract class CarBuilder {
//建造一个模型,你要给我一个顺序要,就是组装顺序
public abstract void setSequence(ArrayList<String> sequence);
//设置完毕顺序后,就可以直接拿到这个车辆模型
public abstract CarModel getCarModel();
}
实现类可以定义如下:
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);
}
}
在主类中进行调用:
ArrayList<String> sequence = new ArrayList<String>(); //存放run的顺序
sequence.add("engine boom"); //客户要求,run的时候时候先发动引擎
sequence.add("start"); //启动起来
sequence.add("stop"); //开了一段就停下来
//要一个奔驰车:
BenzBuilder benzBuilder = new BenzBuilder();
//把顺序给这个builder类,制造出这样一个车出来
benzBuilder.setSequence(sequence);
//制造出一个奔驰车
BenzModel benz = (BenzModel)benzBuilder.getCarModel();
//奔驰车跑一下看看
benz.run();
这样便可以针对不同的车子的不同需求进行不同的设计建造。
但是如果要有不同的组合呢?类似于有一个导演指挥这演员的演出顺序等。那我们设计一个导演类来扩展一下。先看类图:
增加了Director类:代码实现
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();
}
...
}
在这个导演类中就可以任意的指派方法的执行顺序了
于是,在Client方法中调用只需如下:
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();
}
}
}
对该案例的总结:
(1)CarModel 以及两个实现类 BenzModel 和 BMWModel 叫做产品类(Product Class),这个产品类实现了模板方法模式,也就是有模板方法和基本方法。
(2)CarBuilder 以及两个实现类 BenzBuilder 和 BMWBuilder 叫做建造者(Builder Class)。
(3)Director 类叫做导演类(Director Class),负责安排已有模块的顺序,然后告诉 Builder 开始建造
这就已经实现了建造者模式了。
总结:
四个要素
产品类:一般是一个较为复杂的对象,也就是说创建对象的过程比较复杂,一般会有比较多的代码量。在本类图中,产品类是一个具体的类,而非抽象类。实际编程中,产品类可以是由一个抽象类与它的不同实现组成,也可以是由多个抽象类与他们的实现组成。
抽象建造者:引入抽象建造者的目的,是为了将建造的具体过程交与它的子类来实现。这样更容易扩展。一般至少会有两个抽象方法,一个用来建造产品,一个是用来返回产品。
建造者:实现抽象类的所有未实现的方法,具体来说一般是两项任务:组建产品;返回组建好的产品。
导演类:负责调用适当的建造者来组建产品,导演类一般不与产品类发生依赖关系,与导演类直接交互的是建造者类。一般来说,导演类被用来封装程序中易变的部分。
注意与工厂设计模式的区别
工厂设计模式侧重于生产的过程,即对象的创建,而建造者模式侧重的是通过不同的执行顺序,让产品类中的调用顺序不同产生了不同的效能。
使用建造者模式的好处:
1.使用建造者模式可以使客户端不必知道产品内部组成的细节。
2.具体的建造者类之间是相互独立的,对系统的扩展非常有利。
3.由于具体的建造者是独立的,因此可以对建造过程逐步细化,而不对其他的模块产生任何影响。
使用建造模式的场合:
1.创建一些复杂的对象时,这些对象的内部组成构件间的建造顺序是稳定的,但是对象的内部组成构件面临着复杂的变化。
2.要创建的复杂对象的算法,独立于该对象的组成部分,也独立于组成部分的装配方法时。