Builder模式
定义
将一个复杂对象的构建与它的表象分离,使得同样的构建可以创建不同的表示。
使用场景
相同的方法,不同的执行顺序,产生不同的事件结果时
多个部件或零件,都可以装配到一个对象中,但是产生的运行结果又不相同时
产品类非常复杂,或者产品类中的调用顺序产生不同作用时
初始化一个对象特别复杂时
实现
经典方案
经典的Builder模式一般拥有4个角色
- Product 产品类或产品抽象类
- Builder 抽象类
- ConcreteBuilder 具体Builder实现类
- Director 组装类
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;
}
}
菜单类Meal在这里充当Product的角色,提供了事物food和饮料drink供客人选择。
public abstract class MealBuilder {
protected Meal meal = new Meal();
public abstract void buildFood();
public abstract void buildDrink();
public Meal getMeal() {
return meal;
}
}
MealBuilder是一个抽象Builder,提供了2个抽象方法buildFood()和buildDrink()供给客人点餐,而食物的品种我们并不知道,具体的食物和套餐是交给子类去实现的。
public class SubMealBuilderA extends MealBuilder {
@Override
public void buildFood() {
meal.setFood("一个鸡腿堡");
}
@Override
public void buildDrink() {
meal.setDrink("一杯可乐");
}
}
SubMealBuilderA 是作为ConcreteBuilder的,他给出了一个具体实现方案---套餐A( 包含一个鸡腿堡和一杯可乐)
public class SubMealBuilderB extends MealBuilder {
@Override
public void buildFood() {
meal.setFood("一个鸡肉卷");
}
@Override
public void buildDrink() {
meal.setDrink("一杯果汁");
}
}
SubMealBuilderB 也是作为ConcreteBuilder的,他给出了一个具体实现方案---套餐B( 包含一个鸡肉卷和一杯果汁)
public class Waiter {
private MealBuilder mealBuilder;
public void setMealBuilder(MealBuilder mealBuilder) {
this.mealBuilder = mealBuilder;
}
public Meal construct() {
mealBuilder.buildDrink();
mealBuilder.buildFood();
return mealBuilder.getMeal();
}
}
Waiter类充当Director的角色,客人将选好的套餐告诉服务员后,服务员将客人选择的食物组装起来并且通知后厨开始生产。
public class Test {
public static void main(String[] args) {
Waiter waiter = new Waiter();
waiter.setMealBuilder(new SubMealBuilderA());
Meal meal = waiter.construct();
log(meal);
waiter.setMealBuilder(new SubMealBuilderB());
meal = waiter.construct();
log(meal);
}
private static void log(Meal meal) {
System.out.println(meal.getClass() + ":" + meal.getFood() + ":" + meal.getDrink());
}
}
运行结果
class com.example.jc.myapplication.builder.custom.Meal:一个鸡腿堡:一杯可乐
class com.example.jc.myapplication.builder.custom.Meal:一个鸡肉卷:一杯果汁
改进方案
在实际中我们更常见的Builder模式是有对经典模式又一定修改的。如AlertDialog的Builer模式,它的AlertDialog.Builer同时扮演了Builder、 ConcreteBuilder、 Director的角色,简化了Builer的使用。同时这种形式的Builer也更常见。
public class WaiterX {
private MenuController mWaiter;
WaiterX() {
mWaiter = new MenuController();
}
public static class Builder {
private MenuController.MenuParams p;
public Builder() {
p = new MenuController.MenuParams();
}
public Builder setFood(String food) {
p.food = food;
return this;
}
public Builder setDrink(String drink) {
p.drink = drink;
return this;
}
public Builder setCount(int count) {
p.count = count;
return this;
}
public Builder setType(int type) {
p.type = type;
return this;
}
public Builder setRemark(String remark) {
p.remark = remark;
return this;
}
public WaiterX create() {
WaiterX waiter = new WaiterX();
p.apply(waiter.mWaiter);
return waiter;
}
}
@Override public String toString() {
return "WaiterX{" + "mWaiter=" + mWaiter + '}';
}
}
在WaiterX中通过一个静态内部类Builder来负责调度产品的组件,再通过create()方法将组装后的整体返回。而具体的装细节被隐藏了,通过交由MenuController.MenuParams去专门处理细节信息。
public class MenuController {
private String food;
private String drink;
/**
* 点单份量
*/
private int count;
/**
* 类型 外带,食堂
*/
private int type;
/**
* 备注
*/
private String remark;
public void setFood(String food) {
this.food = food;
}
public void setDrink(String drink) {
this.drink = drink;
}
public void setCount(int count) {
this.count = count;
}
public void setType(int type) {
this.type = type;
}
public void setRemark(String remark) {
this.remark = remark;
}
public static class MenuParams {
public String food;
public String drink;
public int count;
public int type;
public String remark;
public void apply(MenuController waiter) {
if (drink != null) {
waiter.setDrink(drink);
}
if (food != null) {
waiter.setFood(food);
}
if (count > 1) {
waiter.setCount(count);
} else {
waiter.setCount(1);
}
if (type > 0) {
waiter.setType(type);
} else {
waiter.setType(0);
}
if (remark != null) {
waiter.setRemark(remark);
}
}
}
@Override public String toString() {
return "MenuController{" + (food != null ? "food='" + food + '\'' : "")
+ (drink != null ? ", drink='" + drink + '\'' : "")
+ (count > 0 ? ", count='" + count + '\'' : "")
+ (type > 0 ? ", type='" + type + '\'' : "")
+ (remark != null ? ", remark='" + remark + '\'' : "")
+ '}';
}
}
MenuController负责餐点的具体细节处理,用于记录客户具体点了什么食物、份量、需求等信息
public class Test {
public static void main(String[] args) {
WaiterX waiterX = new WaiterX.Builder().setFood("香辣鸡腿堡")
.setDrink("可乐")
.setType(2)
.setRemark("少盐")
.setCount(3)
.create();
System.out.println(waiterX.toString());
}
}
结果
WaiterX{mWaiter=MenuController{food='香辣鸡腿堡', drink='可乐', count='3', type='2', remark='少盐'}}
通过这种形式,我们在调用的时候就能采用链式调用的方式来处理了,使用起来更加方便。
缺点是 当有新的元素或者新的属性需要添加时候,需要对多个类进行修改。如 要添加一个字段 取餐时间time,则WaiterX和MenuController中都必须对这个字段time进行支持,这就务必要对2个类进行修改了。