組合模式,將對象組合成樹形結構以表示「部分-總體」的層次結構,組合模式使得用戶對單個對象和組合對象的使用具備一致性。掌握組合模式的重點是要理解清楚 「部分/總體」 還有 」單個對象「 與 "組合對象" 的含義。 java
聽懂了這句話就不用往下看了,說明你會了。bash
聽不懂我以爲也正常,若是用一句話能學會就沒人看書了。像我這種笨人,都是學會了一個模式,而後往它的定義上套。函數
也就是說,樹形結構中的菜單,子菜單是容器,菜單項是內容。this
組合模式是要讓容器和內容具備一致性,創造出遞歸結構,而且在某一個場景下,容器和內容能夠無差異使用,圖中第二層既能夠是Leaf,也能夠是Node。spa
常見的場景有:部門樹、功能菜單樹、字典樹等等,在遇到可遞歸的樹形結構需求時,均可以考慮一下是否須要使用組合模式。code
以一個需求爲例進行說明,仍是汽車相關的。component
汽車廠商將汽車分爲品牌、車系、車型、他們以前是屬性關係,品牌下有多個車系,車系下有多個車型,具體例子:對象
品牌是奔馳。blog
奔馳下有多個車系,A級,C級,E級。繼承
每一個車系下有多個車型,A級下有A180,A200,A220。
需求是,打印出全部的車系和車型,在打印時進行名稱美化,傳入車系和車型均可以完成名稱美化。
不使用組合模式來建立這個結構。
一個典型的一對多關係,具體代碼以下:
/** * 品牌 */ public class Brand { private List<Series> series = new ArrayList<Series>(); private String name; public Brand(String name) { this.name = name; } public String getName() { return this.name; } public void add(Series s) { series.add(s); } public void remove(Series s) { series.remove(s); } public Series getChild(int i) { return series.get(i); } public void print() { System.out.println(getName()); Iterator<Series> iterator = series.iterator(); while(iterator.hasNext()) { iterator.next().print(); } } } /** * 車系 */ public class Series { private List<Model> model = new ArrayList<Model>(); private String name; public Series(String name) { this.name = name; } public String getName() { return this.name; } public void add(Model m) { model.add(m); } public void remove(Model m) { model.remove(m); } public Model getChild(int i) { return model.get(i); } public void print() { System.out.println(getName()); Iterator<Model> iterator = model.iterator(); while (iterator.hasNext()) { iterator.next().print(); } } } /** * 車型 */ public class Model { private String name; public Model(String name) { this.name = name; } public String getName() { return this.name; } public void print() { System.out.println(getName()); } } /** * 運行輸出 * 奔馳 * 奔馳A系 * A100 * A200 * 奔馳C系 * C100 * C200 */ public class Client { public static void main(String[] args) { Brand brand = new Brand("奔馳"); Series series = new Series("奔馳A系"); brand.add(series); Model a100 = new Model("A100"); Model a200 = new Model("A200"); series.add(a100); series.add(a200); Series series1 = new Series("奔馳C系"); brand.add(series1); Model c100 = new Model("C100"); Model c200 = new Model("C200"); series1.add(c100); series1.add(c200); brand.print(); } }
需求是,打印出全部的車系和車型,在打印時進行名稱美化,傳入車系和車型均可以完成名稱美化。
第一個弊端 — 通用性差
這裏要寫這個函數就有問題,因爲車系和車型的類型不一樣,因此要單獨編寫函數,實現基本一致:
public String beautiful(Series series) { return "★" + series.getName() + "★"; } public String beautiful(Model model) { return "★" + model.getName() + "★"; }
第二個弊端 — 結構不靈活
如今需求有變動:
車系是一個樹形結構,也就是說Series對象的子對象中,能夠存放車型,也能夠存放車系。
回顧一下Series的代碼:
public class Series { private List<Model> model = new ArrayList<Model>(); ...... }
用於存放子對象的集合,泛型類型是Model,不能保存Series對象。
直接上類圖,使用組合模式後的類圖以下:
回顧概念:
如今,品牌、車系、車型都繼承自組件(Component)。品牌中的集合,泛型類型是Component,車系中的集合泛型類型也是Component。這樣就作到了概念中的兩點,對象仍是樹形結構來表示部分和總體。同時,因爲對象自己繼承自Component,它內部的子對象集合也是Component,因此作到了一致性。
下面是組合模式下的代碼:
/** * 組件對象 */ public abstract class Component { public String getName() { throw new UnsupportedOperationException(); } public void add(Component component) { throw new UnsupportedOperationException(); } public void remove(Component component) { throw new UnsupportedOperationException(); } public Component getChild(int i) { throw new UnsupportedOperationException(); } public void print() { throw new UnsupportedOperationException(); } public String beautiful(Component component) { return "★" + component.getName() + "★"; } } /** * 品牌,繼承自Component */ public class Brand extends Component { // 內部不在是存放具體類型,如今改爲了Component類型,繼承自Component的對象均可以加入 private List<Component> series = new ArrayList<Component>(); private String name; public Brand(String name) { this.name = name; } public String getName() { return this.name; } public void add(Component component) { series.add(component); } public void remove(Component component) { series.remove(component); } public Component getChild(int i) { return series.get(i); } public void print() { System.out.println(getName()); Iterator<Component> iterator = series.iterator(); while(iterator.hasNext()) { iterator.next().print(); } } } /** * 車系,繼承自Component */ public class Series extends Component { // 內部不在是存放具體類型,如今改爲了Component類型,繼承自Component的對象均可以加入 private List<Component> model = new ArrayList<Component>(); private String name; public Series(String name) { this.name = name; } public String getName() { return this.name; } public void add(Component component) { model.add(component); } public void remove(Component component) { model.remove(component); } public Component getChild(int i) { return model.get(i); } public void print() { System.out.println(beautiful(this)); Iterator<Component> iterator = model.iterator(); while (iterator.hasNext()) { iterator.next().print(); } } } /** * 車型 */ public class Model extends Component { private String name; public Model(String name) { this.name = name; } public String getName() { return this.name; } public void print() { System.out.println(beautiful(this)); } }
組合模式的使用方式:
public class Client { public static void main(String[] args) { // 建立品牌 Component brand = new Brand("奔馳"); // 建立車系A Component series = new Series("奔馳A系"); // 把車系A加到品牌 brand.add(series); // 建立兩個車型 Component a100 = new Model("A100"); Component a200 = new Model("A200"); series.add(a100); series.add(a200); // 建立車系C Component series1 = new Series("奔馳C系"); // 加到品牌 brand.add(series1); // 在建立2個車型添加到車系C Component c100 = new Model("C100"); Component c200 = new Model("C200"); series1.add(c100); series1.add(c200); // 這裏!在車型c100和c200平級的節點,建立一個子車系 Component series2 = new Series("奔馳C系運動款"); series1.add(series2); // 在子車系下建立2個車型 Component c100s = new Model("C100S"); Component c200s = new Model("C200S"); series2.add(c100s); series2.add(c200s); // 輸出整個的樹 brand.print(); } } // 輸出結果 奔馳 ★奔馳A系★ ★A100★ ★A200★ ★奔馳C系★ ★C100★ ★C200★ ★奔馳C系運動款★ ★C100S★ ★C200S★
用組合模式解決了需求中的2個點:
1.使用一個函數操做車系,車型
// 可使用一個方式來操做車系、車型、品牌等對象,只要是這個樹結構下的節點都可以操做 public String beautiful(Component component) { return "★" + component.getName() + "★"; }
2.隨時能夠添加子節點
// 在建立2個車型添加到車系C Component c100 = new Model("C100"); Component c200 = new Model("C200"); series1.add(c100); series1.add(c200); // 這裏!在車型c100和c200平級的節點,建立一個子車系 Component series2 = new Series("奔馳C系運動款");
輸出結果是:
★奔馳C系★(車系) |-★C100★(車型) |-★C200★(車型) |-★奔馳C系運動款★(車系) |-★C100S★(車型) |-★C200S★(車型)
樹的任意一級能夠存聽任意的對象(只要是繼承自Component),這種方式解決了第二個需求。
組合模式,個人一個簡單理解是,在兩個場景下應該去考慮使用:
組合模式的作法就很簡單了,主要也是兩點:
以上就是組合模式的一些理解, 有不足之處請你們矯正,謝謝。