09 組合模式(Composite Pattern)
本篇博文說的是組合模式
描述性文字
組合模式,又稱為 部分整體模式,把具有相似的一組物件 當做一個物件處理,用一種樹狀的結構來組合物件,再提供統一的方法去訪問相似的物件,以此忽略掉物件與物件容器間的差別。
舉個栗子
注:此處直接使用的是原圖。
假設這兩類需求如下:
選單:選單名,描述資訊,新增,新增刪除子選單或菜品 遞迴打印出所有的子選單與菜品!
菜品:菜名,描述資訊,價格,列印資訊好的,先試試不用組合模式,要怎麼寫~
不使用組合模式寫選單
示例程式碼:
package structPattrn.compositePattern; import java.util.ArrayList; import java.util.List; /** * 組合模式測試例程 * @Package structPattrn.compositePattern * @Title: CompositePatternDemo.java * @Company: $ * @author BurgessLee * @date 2018年10月25日-下午5:06:05 * @Description: $ */ public class CompositePatternDemo { public static void main(String[] args) { //不用組合模式測試例程 Menu menu = new Menu("大選單","包含所有的子選單"); Menu drinkMenu = new Menu("飲品選單","都是喝的"); Menu eatMenu = new Menu("小吃選單","都是吃的"); MilkTea milkTea = new MilkTea("珍珠賣茶","珍珠+奶茶", 5); Juice juice = new Juice("獼猴桃飲料","無新增劑",5); HandCake handCake = new HandCake("咖哩魚蛋","微辣",6); FishBoll fishBoll = new FishBoll("培根手抓餅","正宗臺灣風味",6); drinkMenu.addMilkTea(milkTea); drinkMenu.addJuice(juice); eatMenu.addHandCake(handCake); eatMenu.addFishBoll(fishBoll); menu.addMenu(drinkMenu); menu.addMenu(eatMenu); System.out.println(menu.toString()); } } //以下部分程式碼使用的不是組合模式實現的========================================================================== class MilkTea{ private String name; private String desc; private int price; public MilkTea(String name, String desc, int price) { super(); this.name = name; this.desc = desc; this.price = price; } @Override public String toString() { return "MilkTea [name=" + name + ", desc=" + desc + ", price=" + price + "]"; } } class Juice{ private String name; private String desc; private int price; public Juice(String name, String desc, int price) { super(); this.name = name; this.desc = desc; this.price = price; } @Override public String toString() { return "Juice [name=" + name + ", desc=" + desc + ", price=" + price + "]"; } } class HandCake{ private String name; private String desc; private int price; public HandCake(String name, String desc, int price) { super(); this.name = name; this.desc = desc; this.price = price; } @Override public String toString() { return "HandCake [name=" + name + ", desc=" + desc + ", price=" + price + "]"; } } class FishBoll{ private String name; private String desc; private int price; public FishBoll(String name, String desc, int price) { super(); this.name = name; this.desc = desc; this.price = price; } @Override public String toString() { return "FishBoll [name=" + name + ", price=" + price + "]"; } } class Menu{ private String name; private String desc; private List<Menu> menus = new ArrayList<>(); private List<MilkTea> milkTeas = new ArrayList<>(); private List<Juice> juices = new ArrayList<>(); private List<HandCake> handCakes = new ArrayList<>(); private List<FishBoll> fishBolls = new ArrayList<>(); public Menu(String name, String desc) { super(); this.name = name; this.desc = desc; } public void addMilkTea(MilkTea milkTea){ this.milkTeas.add(milkTea); } public void addJuice(Juice juice){ this.juices.add(juice); } public void addHandCake(HandCake handCake){ this.handCakes.add(handCake); } public void addFishBoll(FishBoll fishBoll){ this.fishBolls.add(fishBoll); } public void addMenu(Menu menu){ this.menus.add(menu); } @Override public String toString() { return "Menu [name=" + name + ", desc=" + desc + ", menus=" + menus + ", milkTeas=" + milkTeas + ", juices=" + juices + ", handCakes=" + handCakes + ", fishBolls=" + fishBolls + "]"; } }
列印結果:
Menu [name=大選單, desc=包含所有的子選單, menus=[Menu [name=飲品選單, desc=都是喝的, menus=[], milkTeas=[MilkTea [name=珍珠賣茶, desc=珍珠+奶茶, price=5]], juices=[Juice [name=獼猴桃飲料, desc=無新增劑, price=5]], handCakes=[], fishBolls=[]], Menu [name=小吃選單, desc=都是吃的, menus=[], milkTeas=[], juices=[], handCakes=[HandCake [name=咖哩魚蛋, desc=微辣, price=6]], fishBolls=[FishBoll [name=培根手抓餅, price=6]]]], milkTeas=[], juices=[], handCakes=[], fishBolls=[]]
使用組合模式寫選單
示例程式碼:
abstract class AbstractMenu{ public abstract void add(AbstractMenu menu); public abstract AbstractMenu get(int index); public abstract String getString(); } class MileTeaNew extends AbstractMenu{ private String name; private String desc; private int price; public MileTeaNew(String name, String desc, int i) { super(); this.name = name; this.desc = desc; this.price = i; } @Override public void add(AbstractMenu menu) { /*未使用*/ } @Override public AbstractMenu get(int index) { return null; } @Override public String getString() { return toString(); } @Override public String toString() { return "MileTeaNew [name=" + name + ", desc=" + desc + ", price=" + price + "]"; } } class JuiceNew extends AbstractMenu{ private String name; private String desc; private int price; public JuiceNew(String name, String desc) { super(); this.name = name; this.desc = desc; } @Override public void add(AbstractMenu menu) { /*未使用*/ } @Override public AbstractMenu get(int index) { return null; } @Override public String getString() { return toString(); } @Override public String toString() { return "MileTeaNew [name=" + name + ", desc=" + desc + ", price=" + price + "]"; } } class HandCakeNew extends AbstractMenu{ private String name; private String desc; private int price; public HandCakeNew(String name, String desc) { super(); this.name = name; this.desc = desc; } @Override public void add(AbstractMenu menu) { /*未使用*/ } @Override public AbstractMenu get(int index) { return null; } @Override public String getString() { return toString(); } @Override public String toString() { return "MileTeaNew [name=" + name + ", desc=" + desc + ", price=" + price + "]"; } } class FishBollNew extends AbstractMenu{ private String name; private String desc; private int price; public FishBollNew(String name, String desc) { super(); this.name = name; this.desc = desc; } @Override public void add(AbstractMenu menu) { /*未使用*/ } @Override public AbstractMenu get(int index) { return null; } @Override public String getString() { return toString(); } @Override public String toString() { return "MileTeaNew [name=" + name + ", desc=" + desc + ", price=" + price + "]"; } } class MenuNew extends AbstractMenu{ private String name; private String desc; private List<AbstractMenu> menus = new ArrayList<>(); public MenuNew(String name, String desc) { super(); this.name = name; this.desc = desc; } @Override public void add(AbstractMenu menu) { this.menus.add(menu); } @Override public AbstractMenu get(int index) { return this.menus.get(index); } @Override public String getString() { return toString(); } @Override public String toString() { return "MenuNew [name=" + name + ", desc=" + desc + ", menus=" + menus + "]"; } }
測試例程:
MenuNew menuNew = new MenuNew("大選單","包含所有的子選單");
MenuNew drinkMenuNew = new MenuNew("飲品選單","都是喝的");
MenuNew eatMenuNew = new MenuNew("小吃選單","都是吃的");
MileTeaNew milkTeaNew = new MileTeaNew("珍珠賣茶","珍珠+奶茶",6);
JuiceNew juiceNew = new JuiceNew("獼猴桃飲料","無新增劑");
HandCakeNew handCakeNew = new HandCakeNew("咖哩魚蛋","微辣");
FishBollNew fishBollNew = new FishBollNew("培根手抓餅","正宗臺灣風味");
drinkMenuNew.add(milkTeaNew);
drinkMenuNew.add(juiceNew);
eatMenuNew.add(handCakeNew);
eatMenuNew.add(fishBollNew);
menuNew.add(drinkMenuNew);
menuNew.add(eatMenuNew);
menuNew.getString();
使用了合併模式,如果此時我們要新增一個菜品,只需繼承抽象構建類, 無需改動其他類,顯得更加方便。
概念與總結
三個角色
上面也說了合併模式是用一種樹狀的結構來組合物件,三個名詞 根節點,枝結點,葉子結點,類比上面那個選單的圖, 根節點是選單,枝結點是飲料選單和小吃選單, 葉子結點是奶茶,果汁,手抓餅和魚蛋!
Component:抽象元件,為組合中的物件宣告介面,讓客戶端 可以通過這個介面來訪問和管理整個物件結構,可以在裡面為定義的 功能提供預設的實現,比如上面的AbstractMenu類。
Composite:容器元件,繼承抽象元件,實現抽象元件中與 葉子元件相關的操作,比如上面的Menu類重寫了get,set方法。此處重寫的是toString方法也就是,抽象類中的getString方法
Leaf:葉子元件,定義和實現葉子物件的行為,不再包含其它 的子節點物件,比如上面的MilkTea,Juice,HandCakeFishBall。
UML圖
使用情景
- 如果你想表示物件的部分-整體層次結構,可以選用組合模式,把整體和部分的操作統一起來,使得層次結構實現更簡單,從外 部來使用這個層次結構也簡單;
- 如果你希望統一的使用組合結構中的所有物件,可以選用組合模式,這正是組合模式提供的主要功能;
優缺點
優點:
讓客戶端更加簡單,客戶端不需要再操心面對的是組合物件還是葉節點物件,所以不需要寫一大堆if語句來保證他們對正確的物件呼叫了正確 的方法。通常,他們只需要對整個結構呼叫一個方法並執行操作就可以了。
缺點:
容易增加新的元件也會帶來一些問題,比如很難限制組合中的元件型別。 這在需要檢測元件型別的時候,使得我們不能依靠編譯期的型別約束來 完成,必須在執行期間動態檢測。