begin 2018年9月12日08:08:17
建造者模式
定义
将一个复杂的对象的构建和它的表示分离,使得同样的构建过程可以创建不同的表示。 ——《设计模式:可复用面向对象软件的基础》
建造者模式是一种对象创建型模式。
使用场景
从定义中的关键词“复杂的对象”就可以看出来,建造者模式适用于当我们在创建复杂的对象的时候。类似地,“同样的构建过程可以创建不同的表示”,即使客户端执行同样的步骤,也可以得到不一样表示的结果。如:建造者模式可以用于描述KFC如何创建套餐:套餐是一个复杂对象,它一般包含主食(如汉堡、鸡肉卷等)和饮料(如果汁、可乐等)等组成部分,不同的套餐有不同的组成部分,而KFC的服务员可以根据顾客的要求,一步一步装配这些组成部分,构造一个完整的套餐,然后返回给顾客。对于顾客来说,点餐的步骤是固定的,但是从服务员(即下文的指挥者)得到的套餐中主食和饮料是不一样的。
上面为其一,其二还可以作为拥有多个构造参数的需要使用重叠构造器的对象的一种解决方案,使得必要的参数在创建时传入,非必要的参数可以通过建造者的方法设置。后面会有详细的代码示例及分析。
角色
指挥者角色(Director):构建一个使用Builder接口的对象
抽象建造者角色(Builder):为创建一个Product对象的各个部件指定的抽象接口
具体建造者角色(ConcreteBuilder):实现Builder接口,构造和装配各个部件
产品角色(Product)
图示
建造者模式UML类图和序列图:
最终的结果是得到复杂的对象(Complex Object)。本来是客户端直接创建复杂对象,现在是应用建造者模式,通过指挥者指挥具体建造者生成复杂对象的组成部分,然后组装起一个完整的复杂对象。客户端不需要知道复杂对象的构造过程,只需要给指挥者指定具体建造者即可。
放大的类图:
代码示例
指挥者角色(Director.java):
public class Director {
private Builder builder;
// 指定建造者
public Director(Builder builder) {
this.builder = builder;
}
public Product construct() {
builder.setPartA();
builder.setPartB();
builder.setPartC();
return builder.build();
}
}
指挥者对象创建时需要指定建造者。指挥者对象通常拥有一个指挥创建过程及返回复杂产品对象的方法,如construct()。
抽象建造者角色(Builder.java)
public interface Builder {
public void setPartA();
public void setPartB();
public void setPartC();
public Product build();
}
抽象建造者接口确定产品由三个部分组成Part A、Part B、Part C,并声明一个组装产品的方法build()。
具体建造者角色(ConcreteBuilder.java):
public class ConcreteBuilder implements Builder {
private Product product;
public ConcreteBuilder() {
product = new Product();
}
@Override
public void setPartA() {
product.setPartA("Part A");
}
@Override
public void setPartB() {
product.setPartB("Part B");
}
@Override
public void setPartC() {
product.setPartC("Part C");
}
@Override
public Product build() {
return product;
}
}
产品角色(Product.java):
public class Product {
private String partA;
private String partB;
private String partC;
// 省略get、set及toString方法
}
测试类(BuilderTest.java):
public class BuilderTest {
public static void main(String[] args) {
// 建造者
Builder builder = new ConcreteBuilder();
// 指挥者
Director director = new Director(builder);
// 指挥者创建产品返回产品
Product prod = director.construct();
System.out.println(prod); // 输出:Product [partA=Part A, partB=Part B, partC=Part C]
}
}
实例
我们有一辆车,这个车有很多选项。我们可以用建造者模式建造车。
// 指挥者和客户端
public class CarBuildDirector {
private CarBuilder builder;
public CarBuildDirector(CarBuilder builder) {
this.builder = builder;
}
public Car construct() {
return builder.setWheels(4)
.setColor("Red")
.build();
}
public static void main(String[] args) {
CarBuilder builder = new CarBuilderImpl();
CarBuildDirector carBuildDirector = new CarBuildDirector(builder);
Car car = carBuildDirector.construct();
System.out.println(car);
}
}
// 产品
class Car {
private int wheels;
private String color;
public Car() {}
public int getWheels() {
return wheels;
}
public void setWheels(int wheels) {
this.wheels = wheels;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
public String toString() {
return "Car [wheels=" + wheels + ", color=" + color + "]";
}
}
// 抽象建造者
interface CarBuilder {
Car build();
CarBuilder setWheels(int wheels);
CarBuilder setColor(String color);
}
// 具体建造者
class CarBuilderImpl implements CarBuilder{
private Car car;
public CarBuilderImpl() {
this.car = new Car();
}
@Override
public Car build() {
return car;
}
@Override
public CarBuilder setWheels(int wheels) {
car.setWheels(wheels);
// 注意这里返回的是this对象,就是对象本身
// 这样的话就可以在Director使用类似链式调用连续调用这个对象的所有方法
return this;
}
@Override
public CarBuilder setColor(String color) {
car.setColor(color);
return this;
}
}
不同的地方是CarBuilderImpl.setWheels返回的是对象本身,这样我们在创建不同部分的时候可以连续调用对象的方法,代码简化,不好就是不习惯的话就不好理解了。
遇到多个构造器参数时考虑建造者模式
一个类表示包装食品外面的显示的营养成分标签。这些标签中有几个域是必须的:每份的含量、每罐的含量以及每份的卡路里,还有超过20个可选域:总脂肪量、饱和脂肪量、转化脂肪、胆固醇、钠等等。
营养成分类(NutritionFacts.java):
public class NutritionFacts {
private final int servingSize; //(ml) required
private final int servings; //(per container) required
private final int calories; // optional
private final int fat; //(g) optional
private final int sodium; //(mg) optional
private final int carbohydrate; //(g) optional
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);
}
public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate) {
this.servingSize = servingSize;
this.servings = servings;
this.calories = calories;
this.fat = fat;
this.sodium = sodium;
this.carbohydrate = carbohydrate;
}
@Override
public String toString() {
return "NutritionFacts [servingSize=" + servingSize + ", servings=" + servings + ", calories=" + calories
+ ", fat=" + fat + ", sodium=" + sodium + ", carbohydrate=" + carbohydrate + "]";
}
}
这么多参数的构造函数,你怎么记得住。而且如果在未来的某天要增加一个参数什么的,还要修改现有的构造函数、然后添加新的构造函数。
在《Effective Java》第二版第2条的题目就是我上面的小标题,这个例子也是书上的例子。书上第三个解决方法:建造者模式。不直接生成想要的对象,而是让客户端利用所有必要的参数调用构造器(或静态工厂),得到一个builder对象。然后客户端在builder对象上调用类似与setter的方法,来设置每个相关的可选参数。最后,客户端调用哪个无参的build方法来生成不可变的对象。
public class NutritionFactsWithBuilder {
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 {
// Required parameters必要参数
private final int servingSize;
private final int servings;
// Optional parameters - initialized to default values
// 可选参数(初始化默认值)
private int calories = 0;
private int fat = 0;
private int carbohydrate = 0;
private int sodium = 0;
// 创建builder时初始化必要参数
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 carbohydrate(int val) {
carbohydrate = val;
return this;
}
public Builder sodium(int val) {
sodium = val;
return this;
}
public NutritionFactsWithBuilder build() {
return new NutritionFactsWithBuilder(this);
}
}
public NutritionFactsWithBuilder(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
@Override
public String toString() {
return "NutritionFactsWithBuilder [servingSize=" + servingSize + ", servings=" + servings + ", calories="
+ calories + ", fat=" + fat + ", sodium=" + sodium + ", carbohydrate=" + carbohydrate + "]";
}
}
这个缺点也很明显啊,多了一个builder类。
优点
1、客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。用户使用不同的具体建造者即可得到不同的产品对象,新增具体建造者符合“开闭原则”。
2、可以更精细地控制产品的创建过程。
缺点
1、不适用于内部变化复杂的产品。如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。
总结
建造者模式,适用于创建有复杂内部结构的对象,对象属性之间相互依赖,且又可能要使用到一些其他不易得到的对象。
完
图是借用了维基百科英文版的,下面的有关Car代码例子也是。
end 2018年9月12日20:50:17