一、什么是建造者模式
我們先說一個生活中的小例子,當我們在外面飯店吃飯時,比如點個水煮肉片,這家店可能會辣一點、那家店可能會咸一點、對面那家可能放青菜、隔壁那家可能放菠菜,每家店做出來的都不一樣,明明都是水煮肉片卻有不同的做法,如果都一樣就不會說這家難吃那家好吃了。那再看快餐店,比如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 }
運行結果如下: