組合模式定義:組合模式允許你將對象組合成樹形結構來表現“整體/局部”層次結構,組合能讓客戶以一致的方式處理個別對象以及對象組合。
當涉及到如:菜單,子菜單之類的問題時,會自然而然的想到使用樹形結構
類之間的關係確如上圖所示,但是這種設計複用性和擴展性都很低:
1,所有的菜單都有各自的add、remove以及getChild實現,複用性很低
2,類型轉換問題,菜單完全不知道其包含的子元素的具體類型,這是個大問題(List<Object>)
針對上述2個問題:
1,抽象出共同的父類,以實現一些基本方法的複用
2,隱藏菜單與菜單項之間的差異,在這點,我們可以借鑑裝飾者模式,將菜單和菜單項都當做一個菜單,如此一來,共同的父類也出現了
組合模式基本組成:
組件 - Menu、SpecificMenu
葉子節點 - MenuItem
組件可以包含其他組件以及葉子節點
- public abstract class Menu {
- public void add(){
- throw new UnsupportedOperationException();
- }
- public void remove(){
- throw new UnsupportedOperationException();
- }
- public Menu getChild(int i){
- throw new UnsupportedOperationException();
- }
- public void print(){
- throw new UnsupportedOperationException();
- }
- }
- public class MenuItem extends Menu {
- private String name;
- public MenuItem(String name){
- this.name = name;
- }
- public void print(){
- System.out.println(name);
- }
- }
- public class SpecificMenu extends Menu {
- private String name;
- private List<Menu> children = new ArrayList<Menu>();
- public SpecificMenu(String name){
- this.name = name;
- }
- public void add(Menu menu){
- children.add(menu);
- }
- public void remove(Menu menu){
- children.remove(menu);
- }
- public Menu getChild(int i){
- return children.get(i);
- }
- public void print(){
- Iterator<Menu> it = children.iterator();
- while(it.hasNext()){
- Menu tmp = it.next();
- tmp.print();
- }
- }
- }
- public class Cilent {
- public static void main(String[] args) {
- MenuItem mocha = new MenuItem("mocha");
- MenuItem espressos = new MenuItem("espressos");
- SpecificMenu coffeeMenu = new SpecificMenu("coffeeMenu");
- coffeeMenu.add(mocha);
- coffeeMenu.add(espressos);
- MenuItem rice = new MenuItem("rice");
- SpecificMenu restaurantMenu = new SpecificMenu("restaurantMenu");
- restaurantMenu.add(coffeeMenu);
- restaurantMenu.add(rice);
- SpecificMenu allMenu = new SpecificMenu("allMenu");
- allMenu.add(restaurantMenu);
- allMenu.print();
- }
- }
測試結果:
- mocha
- espressos
- rice
通常情況下,一個類具有單一的責任纔是好的設計,但是這裏,Menu既要扮演菜單,又需扮演菜單項
在組合模式中,通過犧牲了單一責任的設計原則,換取了透明性(對於一個節點,調用方無法確定其是組合還是節點,即組合與節點對用戶來說是透明的)
爲了保持透明性,組合內所有的對象都必須實現相同的接口,否則將必須考慮哪個對象使用哪個接口,這就失去了組合模式的意義
當然實現相同的接口也意味着有些對象包含一些沒有意義的方法調用(如MenuItem通過繼承依然擁有add、remove以及getChild方法,但是對葉子節點MenuItem來說,操作子節點的方法似乎是沒有意義的,但是換個考慮方向,將MenuItem想象成沒有子節點的節點,是不是感覺不一樣了?)
可以看出這是一個折中的方案(折中方案很常見,比如爲提高處理速度以“空間換時間”),由此也證明設計模式並不是完美無缺的,在使用前有必要仔細審視其帶來的影響
PS:
1,什麼時候使用組合模式:
有一些對象,它們之間具有“整體/部分”之間的關係,並且想用一致的方式對待整體與局部