简说设计模式——建造者模式
一、什么是建造者模式
我们先说一个生活中的小例子,当我们在外面饭店吃饭时,比如点个水煮肉片,这家店可能会辣一点、那家店可能会咸一点、对面那家可能放青菜、隔壁那家可能放菠菜,每家店做出来的都不一样,明明都是水煮肉片却有不同的做法,如果都一样就不会说这家难吃那家好吃了。那再看快餐店,比如KFC,我们点个至尊虾堡,所有人不管在哪个城市哪家店,做法、味道都是一样的,为什么呢,因为它用料、时间、温度等等都是严格规定的,我们只需要下订单就行了,这就是一个建造者模式。
建造者模式(Builder),将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。UML结构图如下:
其中,Director为指挥者/导演类,负责安排已有模块的顺序,然后告诉Builder开始建造;Builder是抽象建造者,规范产品的组建,一般由子类实现;ConcreteBuilder是具体建造者,实现抽象类定义的所有方法,并且返回一个组建好的对象;Product是产品类,通常实现了模板方法模式。
1. Director类
导演类起到封装的作用,避免高层模块深入到建造者内部的实现类。在建造者模式比较庞大时,导演类可以有多个。
1 public class Director { 2 3 public void Construct(Builder builder) { 4 builder.BuildPartA(); 5 builder.BuildPartB(); 6 } 7 8 }
2. Builder类
抽象建造者类,确定产品由两个部件PartA和PartB组成,并声明一个得到产品建造后结果的方法getResult()。
public abstract class Builder { public abstract void BuildPartA(); //产品的A部件 public abstract void BuildPartB(); //产品的B部件 public abstract Product getResult(); //获取产品建造后结果 }
3. ConcreteBuilder类
具体建造者类,有几个产品类就有几个具体的建造者,而且这多个产品类具有相同的接口或抽象类。这里给出一个产品类的样例,多个产品类同理。
1 public class ConcreteBuilder1 extends Builder { 2 3 private Product product = new Product(); 4 5 //设置产品零件 6 @Override 7 public void BuildPartA() { 8 product.add("部件A"); 9 } 10 11 @Override 12 public void BuildPartB() { 13 product.add("部件B"); 14 } 15 16 //组建一个产品 17 @Override 18 public Product getResult() { 19 return product; 20 } 21 22 }
4. Client客户端
1 public class Client { 2 3 public static void main(String[] args) { 4 5 Director director = new Director(); 6 Builder builder1 = new ConcreteBuilder1(); 7 Builder builder2 = new ConcreteBuilder2(); 8 9 //指挥者用ConcreteBuilder1的方法来建造产品 10 director.Construct(builder1); 11 Product product1 = builder1.getResult(); 12 product1.show(); 13 14 //指挥者用ConcreteBuilder2的方法来建造产品 15 director.Construct(builder2); 16 Product product2 = builder2.getResult(); 17 product2.show(); 18 19 } 20 21 }
运行结果如下:
二、建造者模式的应用
1. 何时使用
- 一个基本部件不会变,而其组合经常变化的时候
2. 优点
- 封装性。是客户端不必知道产品内部组成的细节。
- 建造者独立,易扩展。
- 便于控制细节风险。可以对建造过程逐步细化,而不对其他模块产生任何影响。
3. 缺点
- 产品必须有共同点,范围有限制。
- 如果内部变化复杂,会有很多建造类。
4. 使用场景
- 相同的方法,不同的执行顺序,产生不同的事件结果时。
- 需要生成的对象具有复杂的内部结构时。
- 多个部件或零件,都可以装配到一个对象中,但产生的结果又不相同时。
5. 与工厂模式的区别
- 建造者模式更关注于零件装配的顺序
6. 应用实例
- KFC的食品制作流程,原料多少克、加热几分钟等都有严格的规定,我们只需点餐即可,无论在哪里点的都是一样的。
- 去KFC吃汉堡、薯条、炸鸡等,这些单品是不变的,其组合是经常改变的,也就是所谓的“套餐”。
- Java中的StringBuilder/StringBuffer。
三、建造者模式的实现
下面我们看一个KFC点套餐的例子,我们点餐可以点一个汉堡和一个冷饮,汉堡可以是鸡肉汉堡、虾堡等等,是装在盒子中的,冷饮可以是可乐、雪碧等等,是装在瓶子中的。下面我们来用建造者模式对其进行组合,用户只需提交订单即可,UML图如下:
1. Item接口
创建一个表示食物条目和食物包装的接口。
1 public interface Item { 2 3 //获取食物名称 4 public String getName(); 5 //获取包装 6 public Packing packing(); 7 //获取价格 8 public float getPrice(); 9 10 }
1 public interface Packing { 2 3 //获取包装类型 4 public String getPack(); 5 6 }
2. 包装类
实现Packing接口的实现类。Wapper为纸盒包装,Bottle为瓶装。
1 public class Wrapper implements Packing { 2 3 @Override 4 public String getPack() { 5 return "纸盒"; 6 } 7 8 }
1 public class Bottle implements Packing { 2 3 @Override 4 public String getPack() { 5 return "纸杯"; 6 } 7 8 }
3. 食品类
创建实现Item接口的抽象类。Burger为汉堡,Drink为饮品。
1 public abstract class Burger implements Item { 2 3 @Override 4 public Packing packing() { 5 return new Wrapper(); 6 } 7 8 @Override 9 public abstract float getPrice(); 10 11 }
1 public abstract class Drink implements Item { 2 3 @Override 4 public Packing packing() { 5 return new Bottle(); 6 } 7 8 @Override 9 public abstract float getPrice(); 10 11 }
4. 具体食品类
创建扩展了Burger和Drink的具体实现类。这里简单的就设为Burger1、Burger2、Drink1、Drink2。各写一个,多余的就不赘述了。
1 public class Burger1 extends Burger { 2 3 @Override 4 public String getName() { 5 return "汉堡1"; 6 } 7 8 @Override 9 public float getPrice() { 10 return 25.0f; 11 } 12 13 }
1 public class Drink1 extends Drink { 2 3 @Override 4 public String getName() { 5 return "饮品1"; 6 } 7 8 @Override 9 public float getPrice() { 10 return 15.0f; 11 } 12 13 }
5. Meal类
1 public class Meal { 2 3 private List<Item> items = new ArrayList<>(); 4 5 public void addItem(Item item) { 6 items.add(item); 7 } 8 9 //获取总消费 10 public float getCost() { 11 float cost = 0.0f; 12 13 for (Item item : items) { 14 cost += item.getPrice(); 15 } 16 17 return cost; 18 } 19 20 public void showItem() { 21 for (Item item : items) { 22 System.out.print("餐品:" + item.getName()); 23 System.out.print(",包装:" + item.packing().getPack()); 24 System.out.println(",价格:¥" + item.getPrice()); 25 } 26 } 27 28 }
6. 指挥者
1 public class MealBuilder { 2 3 //订单1 4 public Meal order1() { 5 Meal meal = new Meal(); 6 meal.addItem(new Burger1()); 7 meal.addItem(new Drink1()); 8 9 return meal; 10 } 11 12 //订单2 13 public Meal order2() { 14 Meal meal = new Meal(); 15 meal.addItem(new Burger2()); 16 meal.addItem(new Drink2()); 17 18 return meal; 19 } 20 21 }
7. Client客户端
1 public class Client { 2 3 public static void main(String[] args) { 4 MealBuilder mealBuilder = new MealBuilder(); 5 6 //获取第一个订单 7 Meal order1 = mealBuilder.order1(); 8 System.out.println("------order1------"); 9 order1.showItem(); 10 System.out.println("总额:¥" + order1.getCost()); 11 12 //获取第二个订单 13 Meal order2 = mealBuilder.order2(); 14 System.out.println("------order2------"); 15 order2.showItem(); 16 System.out.println("总额:¥" + order2.getCost()); 17 } 18 19 }
运行结果如下: