最近看了很多设计模式的资料,看到别人的一句话,感觉说的特别好。“我发现学一门设计模式,不管你是否能立刻理解它,第一最要紧的是要记住两个东西。1、它是属于什么范畴的设计模式。2、记住UML图” 好, 现在进入正题
1. 概述
建造者模式(Builder):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。属于创建类模式.
2. 个人理解
建造者模式属于创建型模式。 一会再上UML图
按照一套的构建方式,生产出不同的产品(表示)出来。感觉和官方说的一样,哈哈,那还是再解释一下:
可以分开理解:
1)将一个复杂对象的构建与它的表示分离。一般创建对象的时候很多都是调用构造函数去创建, 建造者模式就是创建对象的任务,并不写在产品类里面,而是和产品类分开,这就是上面说的第一点:将一个复杂对象的构建与它的表示分离。
2) 使得同样的构建过程可以创建不同的表示。 想要创建不同的对象,之前是使用不同的构造方法,这里最终创建的方式是一样的,或者说创建的过程是一样的,只是创建出来不同的产品(表示)
下面这句话是只想说给自己,有不同意见的请绕行。任何一种设计模式都是为了解决某个问题而存在的,但设计模式看了多少遍,由于本人记性特别不好,到最后经常还是会忘记它的来源,忘记为什么要使用这样的模式,所以重要的是看完某个设计模式以后,实际应用中不管是否记为什么要用这种模式,记着遇到同样的场景就先记着使用这样的模式好了,要不就会走到一个怪圈:老是去想为什么要用,而一直重复学习。
3. 来源
网上有很多都解释建造者模式到底怎么使用,但说明为什么有这样的设计模式,建造者怎么产生出来的,并没有很多文章介绍,这里详细解释一下
举例说明: (之前说过了,本人做测试开发,所以很多例子都从测试出发,不感兴趣请绕行)
工作中我们有个这样的场景:有一个 Case类,有如下大量的属性,让我们假设你想让这个对象不可变(顺便说一句,除非你有一个真正的好理由,让你不必总是向着不可变这个目标奋斗。可以网上查看Java不可变对象的好处)
public class Case {
private final long id; //optional
private final String name;//required
private final String project_name; //required
private final String caseType; //optional
private ArrayList<CaseStep> caseSteps; //required
...
}
有些属性是必须要设置的,有些是不必要设置的。现在需要保存这个case ,根据情况的不同,需要设置不同的属性,所有的属性都被声明成final类型,所以你必须在构造方法中设置它们(final类的必须实现初始化,一种是直接赋值,一种是构造方法中赋值,final类的属性不能有setter方法),但是你又希望别人在创建的时候可以根据不同的需求,只需要设置那些自己需要的属性,别的属性不去设置,该怎么办呢? 那就创建不同的构造方法呗, 如下显示
public Case(String name, String projectName){
this.id = 1;
this.name = name;
this.caseSteps = null;
this.projectName = projectName;
this.caseType = "API";
}
public Case(String name, String projectName, ArrayList<CaseStep> caseSteps){
this.id = 1;
this.name = name;
this.caseSteps = caseSteps;
this.projectName = projectName;
this.caseType = "API";
}
public Case(String name, String projectName, ArrayList<CaseStep> caseSteps,long id, String caseType){
this.id = id;
this.name = name;
this.caseSteps = caseSteps;
this.projectName = projectName;
this.caseType = caseType;
}
public Case(String name, String projectName, String caseType){
this.id = 1;
this.name = name;
this.caseSteps = null;
this.projectName = projectName;
this.caseType = caseType;
}
但是大家都能看出来上面有什么毛病吧,要是场景很多呢?创造很多的构造函数?随着属性个数的增加,构造函数会代码变的越来越难阅读和维护。更重要的是,对客户端来说代码变得越来越难使用。如果属性很多,那构造函数的参数的长度会很长,代目
客户端想我该调用那一个构造方法?有两个参数那个?三个参数那个?我没有显示传值的那些参数的默认值都是什么?我只想给我关心的参数赋值,别的参数不想管,但是没有这样的构造函数,比如上面构造函数里我只想赋值 name/projectName/id 但是我找不到这样的构造函数,我只能知道到
public Case(String name, String projectName, ArrayList<CaseStep> caseSteps,long id, String caseType)
的方法,我必须要给我不想管的参数传一些值,一是我需要虚造出数据来,二是不我不保证这样的数据是正确的。那该怎么办呢? 当然,如果把所有final类的都设置为非final 的,不需要构造函数,只需要 给每个属性设置set /get方法就好了。 但是这样违背了上面提到的”Java不可变对象“的原则,所以这种方式不考虑, 那怎么办呢? 建造者模式就可以满足以上的要求,直接看代码好了。
public class Case1 {
private final long id; // optional
private final String name; // required
private final String projectName; // required
private final String caseType; // optional
private final ArrayList<CaseStep> caseSteps; // optional
private Case1(CaseBuilder caseBuilder) {
this.id = caseBuilder.id;
this.name = caseBuilder.name;
this.projectName = caseBuilder.projectName;
this.caseSteps = caseBuilder.caseSteps;
this.caseType = caseBuilder.caseType;
}
public static class CaseBuilder {
private final String name; // required
private final String projectName; // required
private long id; // optional
private String caseType; // optional
private ArrayList<CaseStep> caseSteps; // optional
public CaseBuilder(String name, String projectName) {
this.name = name;
this.projectName = projectName;
}
public Case1 build() {
return new Case1(this);
}
public CaseBuilder setId(long id) {
this.id = id;
return this;
}
public CaseBuilder setCaseType(String caseType) {
this.caseType = caseType;
return this;
}
public CaseBuilder setCaseSteps(ArrayList<CaseStep> caseSteps) {
this.caseSteps = caseSteps;
return this;
}
}
}
客户端的使用方式
public class Client {
public static void main(String[] args) {
Case1 caseInfo = new Case1.CaseBuilder("name", "projectName").setCaseType("API").setId(2).build();
}
}
这样的好处: 1:可以按照自己的需求生成不同的产品(表达) 2:客户端使用简单,哪些是必须的,哪些不是必须的, 一清二楚,可以随意的设置属性 3: 创建过程放在一行代码里
之前做过安卓,安卓的AlertDialog就是使用这样的方式
但是建造者模式在实际使用中除了这种方法,还可以使用在比这个复杂的使用场景,比如上面的构建只是但是的构建,比如某一天:
Case1 caseInfo = new Case1.CaseBuilder("name", "projectName").setCaseType("API").setId(2).build();
这样的构建,客户端都不想知道,或者说可以帮他当做一个模板提供,可以直接调用就好了。我不需要知道里面的具体袭击额,具体怎么build出来的。 那就是真正意义上的建造者模式了
4. 结构
建造者模式通常包括以下这几个角色:
1、Builder:给出一个抽象接口,规范建造者对于生产的产品的各个组成部分的建造。这个接口只是定一个规范,不涉及具体的建造,具体的建造让继承于它的子类(ConcreteBuilder)去实现。
2、ConcreteBuilder:实现builder接口,针对不同的商业逻辑,具体化各对象部分的建造,最后返回一个建造好的产品。
3、Director:导演,顾名思义,负责规范流程之用。在指导中不涉及产品的创建,只负责保证复杂对象各部分被创建或按某种顺序创建。
4、Product:复杂对象。
5. 举例
有很多场景可以使用这样的设计 eg:
1)游戏设计
比如在玩“极品飞车”这款游戏,那么每一关的地图会千变万化,简单的来说,地图会有晴天和阴天之分,那么创建地图时就要根据晴天或者阴天来对地图上的场景,比如:天空,树,房子,和路面进行渲染,这个过程是一个固定的,每创建一个新地图都要执行这几个渲染,这是针对高级配置的电脑来说的。
现在拥有低配置电脑的人不在少数,那么他们就不能玩游戏了吗?完全可以!只要将地图中占用资源比较高的渲染去掉就可以,比如带反射光影的树,这时候需要创建不同的地图,但地图的创建过程却是固定的,建造者模式完全可以应对这样的情况。
2)KFC例子
去肯德基,汉堡、可乐、薯条、炸鸡翅等是不变的,而其组合是经常变化的,生成出所谓的"套餐
下面以KFC为例,直接看代码
Product 类
package Builder.kfc;
public class Meal {
private String food;
private String drink;
public String getFood() {
return food;
}
public void setFood(String food) {
this.food = food;
}
public String getDrink() {
return drink;
}
public void setDrink(String drink) {
this.drink = drink;
}
}
Build 类
public abstract class MealBuilder {
//Product的实例
Meal meal = new Meal();
public abstract void buildFood();
public abstract void buildDrink();
//真正创建Product的方法
public Meal getMeal(){
return meal;
}
}
public class MealABuilder extends MealBuilder{
public void buildDrink() {
meal.setDrink("一杯可乐");
}
public void buildFood() {
meal.setFood("一盒薯条");
}
}
public class MealBBuilder extends MealBuilder{
public void buildDrink() {
meal.setDrink("一杯柠檬果汁");
}
public void buildFood() {
meal.setFood("三个鸡翅");
}
}
package Builder.kfc;
/**
* 最后是KFC的服务员,它相当于一个指挥者,它决定了套餐是的实现过程,然后给你一个完美的套餐
*/
public class KFCWaiter {
private MealBuilder mealBuilder;
public void setMealBuilder(MealBuilder mealBuilder) {
this.mealBuilder = mealBuilder;
}
/**
* 有点像模板方式一样,只不过模板方式是放在父类里面,建造者是放在Director 类里面
* @return
*/
public Meal construct(){
//准备食物
mealBuilder.buildFood();
//准备饮料
mealBuilder.buildDrink();
//准备完毕,返回一个完整的套餐给客户
return mealBuilder.getMeal();
}
}
public class Client {
public static void main(String[] args) {
//服务员 Director类
KFCWaiter waiter = new KFCWaiter();
//套餐A Builder类
MealABuilder a = new MealABuilder();
//服务员准备套餐A 设置Director类 的build属性
waiter.setMealBuilder(a);
//获得套餐 使用Director创建 Product
Meal mealA = waiter.construct();
System.out.print("套餐A的组成部分:");
System.out.println(mealA.getFood()+"---"+mealA.getDrink());
MealBBuilder b = new MealBBuilder();
//服务员准备套餐A
waiter.setMealBuilder(b);
//获得套餐
Meal mealb = waiter.construct();
System.out.print("套餐B的组成部分:");
System.out.println(mealA.getFood()+"---"+mealb.getDrink());
}
}
6. 总结
如果你遇到一个类,构建他的方式有很多中,就像上面的case, 你可以使用建造者方法,准确的来书这个不是模式,只是一种方法
如果你遇到一个需要把控流程,但流程中的实现细节各有许多的方式,你可以采用建造者模式
当然建造者并以一定局限于使用父类来当Build.也可以使用接口 (转: http://www.cnblogs.com/java-my-life/archive/2012/04/07/2433939.html)
7. 补充
如果你使用eclipse作为IDE,建造者其实书写方式都差不多,现在已经有相当多的插件来避免编写建造者模式大部分的重复代码劳动。仅举三个例子:
http://code.google.com/p/bpep/
http://code.google.com/a/eclipselabs.org/p/bob-the-builder/
http://code.google.com/p/fluent-builders-generator-eclipse-plugin/