一、介绍
建造者模式(Builder Pattern)属于创建型设计模式,很多博客文章的对它的作用解释为用于将复杂对象的创建过程与其细节表示分离。但对于初学者来说,这句话难免有点晦涩难懂,何为复杂对象?何为创建过程,何为细节表示?
复杂对象:一个对象中的成员属性出了基本数据类型及其对应的封装类型,还包含有其他类型对象。例如:在对象A中包含对象B的引用,对象B中又包含对象C的引用。
创建过程:一个对象的实例化和初始化过程。如new
一个对象后再调用其set
方法为其初始化。
细节表示:在对一个对象初始化时,通过set
方法表示其创建的细节。说白了,细节表示就是该对象的成员变量。
在一般情况下,我们要创建一个完整的对象时,往往是通过两个步骤完成:①实例化(即new
一个对象);②初始化(即调用set
方法对其属性赋值)。这是一种创建过程与细节表示耦合的情况。即:一个对象的实例化和初始化过程就表示为其创建过程;而在初始化过程中通过set
方法我们又了解到了其内部属性(即细节表示)。
而在建造者模式中,我们只需要通过一个对象的建造者(Builder)所提供的方法向其描述对象的细节,该然后该建造者通过其核心方法build()
将对象实例化并补充其细节。这就是建造者的核心思想。
二、应用
- java中的
StringBuilder
类。我们通过其提供的各种重载的append()
方法,描述一个字符串的细节,最后通过toString()
方法获得String
实例。 - OkHttp中应用了大量的建造者模式。如创建请求客户端时,使用
OkHttpClient
的内部类Builder
来描述请求的细节,然后通过build()
方法获得一个OkHttpClient
实例。
三、案例
1. 麦当劳1+1随心配
我们以麦当劳随心配1+1套餐为例,当用户选择该套餐时,该套餐规定选择一个任意主食 + 一个任意冷饮。如下图所示
选择主食
选择冷饮
添加到购物车后,我们可以看到该套餐详情
2. 代码演示
新建套餐抽象接口
Meal
,以及实现该接口的随心配1+1套餐SuiXinPeiMeal
```java
public interface Meal {// 套餐价格 Float getCost(); // 套餐详情 void order();
}
public class SuiXinPeiMeal implements Meal{
// 主食
private Food food;
// 冷饮
private ColdDrink coldDrink;
// 省略get、set方法...
// 套餐价格
@Override
public Float getCost() {
return 5F;
}
// 套餐详情
@Override
public void order() {
System.out.println("随心配1+1套餐:");
System.out.println("主食:" + food.getName() + ",单价:" + food.getCost());
System.out.println("冷饮:" + coldDrink.getName() + ",单价:" + coldDrink.getCost());
System.out.println("套餐价格:" + getCost());
}
}
- 新建一个随心配1+1套餐的创建者`SuiXinPeiMealBuilder`:
```java
public class SuiXinPeiMealBuilder {
private Food food;
private ColdDrink coldDrink;
// 向套餐中添加主食
public void addFood(Food food) {
this.food = food;
}
// 向套餐中添加冷饮
public void addColdDrink(ColdDrink coldDrink) {
this.coldDrink = coldDrink;
}
// 创建套餐
public Meal build() {
if (food == null) {
throw new RuntimeException("请选择一个主食");
}
if (coldDrink == null) {
throw new RuntimeException("请选择一个冷饮");
}
SuiXinPeiMeal meal = new SuiXinPeiMeal();
meal.setFood(food);
meal.setColdDrink(coldDrink);
return meal;
}
}
新建单品抽象接口
SingleProduct
public interface SingleProduct { // 单品名称 String getName(); // 单品价格 Float getCost(); }
新建主食抽象接口
Food
、及其实现类Chicken
鸡块、Fries
薯条、Hamburger
汉堡public interface Food extends SingleProduct { } public class Chicken implements Food{ @Override public String getName() { return "鸡块"; } @Override public Float getCost() { return 13F; } } public class Fries implements Food{ @Override public String getName() { return "薯条"; } @Override public Float getCost() { return 10F; } } public class Hamburger implements Food { @Override public String getName() { return "汉堡"; } @Override public Float getCost() { return 15F; } }
新建冷饮抽象接口
ColdDrink
、及其实现类CocaCola
可乐、NoSugarCola
无糖可乐、Sprite
雪碧public interface ColdDrink extends SingleProduct { } public class CocaCola implements ColdDrink{ @Override public String getName() { return "可乐"; } @Override public Float getCost() { return 3.8F; } } public class NoSugarCola implements ColdDrink { @Override public String getName() { return "无糖可乐"; } @Override public Float getCost() { return 5.0F; } } public class Sprite implements ColdDrink { @Override public String getName() { return "雪碧"; } @Override public Float getCost() { return 8F; } }
在
main()
方法中进行套餐模拟public static void main(String[] args) { // 套餐:鸡块+可乐 SuiXinPeiMealBuilder builder1 = new SuiXinPeiMealBuilder(); // 选择鸡块 builder1.addFood(new Chicken()); // 选择可口可乐 builder1.addColdDrink(new CocaCola()); // 创建订单 Meal meal1 = builder1.build(); // 输出订单详情 meal1.order(); System.out.println("=============================================="); // 套餐:汉堡+雪碧 SuiXinPeiMealBuilder builder2 = new SuiXinPeiMealBuilder(); builder2.addFood(new Hamburger()); builder2.addColdDrink(new Sprite()); Meal meal2 = builder2.build(); meal2.order(); }
3. 演示结果
运行上述代码,结果如下
四、优缺点
- 优点
- 对象的创建过程与其细节表示分离,即解耦。
- 缺点:
- 产生多余的建造者Builder类,且每当添加一种套餐,就需要多写一个对应的套餐建造者类。
- 建造者与对象之间耦合,当修改对象结构时,建造者也要修改。
- 可读性相对较差。
五、送给读者
- 在非必要的情况下,业务代码中不建议使用该设计模式,没有性能上的优化却导致可读性变差,属于炫技型设计模式。
- 如果本文对你有所帮助,别忘了安排博主一顿麦当劳随心配1+1套餐哦。
纸上得来终觉浅,绝知此事要躬行。
————————我是万万岁,我们下期再见————————