建造者模式是对象的创建模式,可以将一个产品的内部表象与产品的生成过程分割开来,从而可以使一个建造过程生成具有不同的内部表下那个的产品对象。
也就是说使用建造者模式,可以使客户端不需要知道所生成的产品对象有哪些零件,每个产品对应两件彼此有何不同,是怎么建造出来的,以及怎样组成产品。
建造者模式涉及四个角色
1、抽象建造者(Builder):给出一个抽象接口,以规范产品对象的各个组成成分的建造,接口中申明产品的建造方法,和结果的返还方法。
2、具体建造者(ConcreteBuilder):实现抽象建造者Builder所声明的接口。并在建造完成后提供产品的实例。
3、导演者(Director):调用具体建造者去创建产品对象。
4、产品类(Product):产品便是建造中的复杂对象,一般来说,一个系统中会有多于一个的产品类,而且这些产品类不一定有共同的接口,而完全可以使不相关联的。
导演角色将创建产品的请求委派给具体建造者,具体建造者根据建造方法建造完成,并提供产品的实例,然后产品类去接收产品的实例。
看看下面的例子:
Builder.java(抽象建造者)
abstract public class Builder {
/**
* 产品零件构造方法
*/
public abstract void buildPart1();
/**
* 产品零件构造方法
*/
public abstract void buildPart2();
/**
* 产品返还方法
* @return
*/
public abstract Product retrieveResult();
}
Product.java(产品类)
public class Product {
private List<String> list = new ArrayList<>();
public void addStr(String str){
list.add(str);
}
public void show(){
for(String s:list){
System.out.println("合体:"+s);
}
System.out.println("合体成功!");
}
}
ConcreteBuilder.java(具体建造者)集成抽象建造者,并实现产品的建造方法。最后返还产品的实例。
public class ConcreteBuilder extends Builder {
private Product product = new Product();
@Override
public void buildPart1() {
product.addStr("我来组成头部!");
}
@Override
public void buildPart2() {
product.addStr("我来组成身体- -");
}
@Override
public Product retrieveResult() {
return product;
}
}
Director.java(导演者)调用具体建造者创建产品。
public class Director {
/**
* 创建产品
* @param builder
*/
public void Construct( Builder builder){
builder.buildPart1();
builder.buildPart2();
builder.retrieveResult();
}
}
Client.java(客户端调用)
public class Client {
public static void main(String[] args) {
//先实例导演和具体建造者
Director director = new Director();
Builder builder = new ConcreteBuilder();
//导演者调用具体建造者去建造产品
director.Construct(builder);
//产品类去接收组装好的产品
Product product = builder.retrieveResult();
//产品展示
product.show();
}
输出结果
合体:我来组成头部!
合体:我来组成身体- -
合体成功!
以上是经典的Builder模式。在《Effective Java》这本书中,第二条是:当构造方法参数过多时使用builder模式,这里的Builder模式又是怎么使用的呢,下面我们来看看。
举个例子,一个食品包装上营养成分的类(NutritionFacts),里面有几个必需的属性——每次建议的摄入量,每罐的份量和每份卡路里 ,以及超过20个可选的属性——总脂肪、饱和脂肪、反式脂肪、胆固醇、钠等等。
public class NutritionFacts {
private final int servingSize; // 必须的属性
private final int servings; // 必须的属性
private final int calories; // 可选的属性
private final int fat; //可选的属性
private final int sodium; // 可选的属性
private final int carbohydrate; // 可选的属性
}
如何创建这个对象呢?一种可选的方法是使用构造方法,第一个构造方法只包含两个必需的参数,第二个构造方法中,增加一个可选参数,第三个构造方法中再增加一个可选参数,依次类推,直到构造方法中包含了所有的参数。
public NutritionFacts(int servingSize, int servings) {
this(servingSize, servings, 0);
}
public NutritionFacts(int servingSize, int servings,
int calories) {
this(servingSize, servings, calories, 0);
}
public NutritionFacts(int servingSize, int servings,
int calories, int fat) {
this(servingSize, servings, calories, fat, 0);
}
public NutritionFacts(int servingSize, int servings,
int calories, int fat, int sodium) {
this(servingSize, servings, calories, fat, sodium, 0);
}
这样做,参数较少的时候问题还不大,一旦参数多了,代码可读性就很差,并且难以维护,如果一旦写反了两个参数的值,编译不会报错,但很明显我们会出bug。
第二种方式:当在构造方法中遇到许多可选参数时,另一种选择是JavaBeans模式,在这种模式中,调用一个无参数的构造函数来创建对象,然后调用setter方法来设置每个必需的参数和可选参数。
public class NutritionFacts {
private int servingSize = -1; // 必须的属性;
private int servings = -1; // 必须的属性;
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 0;
public NutritionFacts() { }
// Setters
public void setServingSize(int val) { servingSize = val; }
public void setServings(int val) { servings = val; }
public void setCalories(int val) { calories = val; }
public void setFat(int val) { fat = val; }
public void setSodium(int val) { sodium = val; }
public void setCarbohydrate(int val) { carbohydrate = val; }
}
这种方法看起来可读性不错,而且易于维护。调用者,只需要创建一个空的对象,然后传入我们需要的参数就可以了。
1、对象会产生不一致的状态。当你想要传入5个参数的时候,你必需将所有的setXX方法调用完成之后才行。然而一部分的调用者看到了这个对象后,以为这个对象已经创建完毕,就直接使用了,其实对象并没有创建完成。
2、类是可变的了,不可变类所有好处都不复存在。
卖了这么多关子,就是为了来说说我们的第三种方法Builder模式。
package com.lw.study.designModel.BuilderPattern;
/**
* @author:
* @Date: 14:15 2018/7/24
* 营养成分
*/
public class NutritionFacts {
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;
public static class Builder{
private final int servingSize;
private final int servings;
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 0;
public Builder(int servingSize, int servings) {
this.servingSize = servingSize;
this.servings = servings;
}
public Builder calories(int val) {
calories = val;
return this;
}
public Builder fat(int val) {
fat = val;
return this;
}
public Builder sodium(int val) {
sodium = val;
return this;
}
public Builder carbohydrate(int val) {
carbohydrate = val;
return this;
}
public NutritionFacts build() {
return new NutritionFacts(this);
}
}
private NutritionFacts(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
}
它是Builder模式的一种形式,客户端不直接调用所需的对象,而是调用构造方法(或静态工厂),并使用所有必需的参数,并获得一个builder对象。然后,客户端调用builder对象的setter相似方法来设置每个可选参数。最后,客户端调用一个无参的build方法来生成对象,该对象通常是不可变的。就像下面这样:
NutritionFacts coca = new NutritionFacts.Builder(120,20).calories(20)
.carbohydrate(40)
.build();
NutritionFacts类是不可变的,所有的参数默认值都在一个地方。builder的setter方法返回builder本身,这样调用就可以被链接起来,从而生成一个流畅的API。