《Head First 設計模式》:組合模式

正文

1、定義

組合模式容許你將對象合成樹形結構來表現「總體/部分」層次結構。組合能讓客戶以一致的方式處理組合對象以及個體對象。ide

  • 組合對象:包含其餘組件的組件。
  • 個體對象(葉節點對象):沒有包含其餘組件的組件。

要點:this

  • 組合結構內的任意對象稱爲組件,組件能夠是組合,也能夠是葉節點。
  • 經過將組合對象和個體對象放在樹形結構中,咱們建立了一個「總體/部分」層次結構。若是將整個樹形結構視爲一個「大組合」的話,那麼這個樹形結構的每個「子樹形結構」也是一個組合,包括葉節點也能夠被視爲一個不包含其餘對象的組合。這樣一來,咱們就有了「以一致的方式處理」的基礎了。
  • 所謂「以一致的方式處理」,是指組合和葉節點具備共同的方法能夠調用。這就要求它們必須實現相同的接口。
  • 組合模式容許客戶對組合對象和個體對象一視同仁。換句話說,咱們能夠把相同的操做應用在組合對象和個體對象上。

2、實現步驟

一、建立組件抽象類

也可使用組件接口。code

組件中有些方法可能不適合某種對象,此時咱們能夠拋異常或者提供特定實現。component

/**
 * 組件抽象類
 */
public abstract class Component {
    
    /**
     * 子組件(能夠是組合或葉節點)
     */
    protected List<Component> childs = new ArrayList<Component>();

    /**
     * 添加子組件
     */
    public void addChild(Component component) {
        childs.add(component);
    }
    
    /**
     * 移除子組件
     */
    public void removeChild(Component component) {
        childs.remove(component);
    }
    
    /**
     * 獲取全部子組件
     */
    public List<Component> getChilds() {
        return childs;
    }
    
    public String getName() {
        // 默認拋異常,由子類決定要不要覆蓋
        throw new UnsupportedOperationException();
    }
}

二、建立組合及葉節點,並繼承組件抽象類

(1)組合

組合能夠包含其餘組合,也能夠包含葉節點。對象

/**
 * 組合
 */
public class Composite extends Component {

    private String name;
    
    public Composite(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }
}

(2)葉節點

葉節點沒法添加、刪除、獲取子節點,所以須要對相應的方法進行特殊處理。blog

/**
 * 葉節點
 */
public class Leaf extends Component {

    private String name;
    
    public Leaf(String name) {
        this.name = name;
    }
    
    @Override
    public void addChild(Component component) {
        // 葉節點不能添加子節點,能夠拋異常或者空實現
        throw new UnsupportedOperationException();
    }

    @Override
    public void removeChild(Component component) {
        // 葉節點沒有子節點可移除,能夠拋異常或者空實現
        throw new UnsupportedOperationException();
    }
    
    @Override
    public List<Component> getChilds() {
        // 葉節點沒有子節點,能夠拋異常或者返回空集合
        throw new UnsupportedOperationException();
    }
    
    @Override
    public String getName() {
        return name;
    }
}

三、統一使用組件的方法,操做組合及葉節點

因爲組合及葉節點都實現了組件接口,所以可使用組件的方法來操做組合及葉節點。繼承

public class Test {

    public static void main(String[] args) {
        // 組合
        Component composite1 = new Composite("composite1");
        Component composite2 = new Composite("composite2");
        Component composite3 = new Composite("composite3");
        // 葉節點
        Component leaf1 = new Leaf("leaf1");
        Component leaf2 = new Leaf("leaf2");
        Component leaf3 = new Leaf("leaf3");
        Component leaf4 = new Leaf("leaf4");
        
        // 組合1包含組合二、3
        composite1.addChild(composite2);
        composite1.addChild(composite3);
        // 組合2包含葉節點一、2
        composite2.addChild(leaf1);
        composite2.addChild(leaf2);
        // 組合3包含葉節點三、4
        composite3.addChild(leaf3);
        composite3.addChild(leaf4);
        
        // 打印組件名稱
        System.out.println(composite1.getName());
        for (Component child : composite1.getChilds()) {
            System.out.println(" " + child.getName());
            for (Component leaf : child.getChilds()) {
                System.out.println("  " + leaf.getName());
            }
        }
    }
}

3、舉個栗子

一、背景

對象村餐廳和對象村煎餅屋合併了,它們合併後的新公司建立了一個 Java 版本的女招待。這個 Java 版本的女招待可以打印使用 ArrayList 存儲的餐廳菜單和煎餅屋菜單。接口

如今它們打算加上一份餐後甜點的「子菜單」。也就是說,這個 Java 版本的女招待不只要支持打印多個菜單,還要支持打印菜單中的菜單。rem

二、實現

因爲菜單可能包含菜單和菜單項,所以咱們能夠建立一個樹形結構,這個樹形結構由菜單和菜單項組成。get

經過將菜單和菜單項放在相同的結構中,咱們既能夠把整個結構視爲一個「大菜單」,也能夠把這個結構的任一部分視爲一個「子菜單」,包括菜單項也能夠視爲一個「我自己就是菜單項所以不必再包含菜單項的菜單」。這樣,咱們就能夠以一致的方式來處理菜單和菜單項了。

(1)建立菜單組件抽象類

/**
 * 菜單組件抽象類
 */
public abstract class MenuComponent {
    
    /**
     * 子組件(能夠是菜單或菜單項)
     */
    protected List<MenuComponent> childs = new ArrayList<MenuComponent>();
    
    /**
     * 添加子組件
     */
    public void addChild(MenuComponent component) {
        childs.add(component);
    }
    
    /**
     * 移除子組件
     */
    public void removeChild(MenuComponent component) {
        childs.remove(component);
    }
    
    /**
     * 獲取全部子組件
     */
    public List<MenuComponent> getChilds() {
        return childs;
    }
    
    public String getName() {
        throw new UnsupportedOperationException();
    }
    
    public double getPrice() {
        throw new UnsupportedOperationException();
    }
    
    public void print() {
        throw new UnsupportedOperationException();
    }
}

(2)建立菜單,並繼承菜單組件抽象類

/**
 * 菜單(組合)
 */
public class Menu extends MenuComponent {

    private String name;
    
    public Menu(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }
    
    @Override
    public void print() {
        System.out.println("\n" + getName());;
        System.out.println("---------------------");;
    }
}

(3)建立菜單項,並繼承菜單組件抽象類

/**
 * 菜單項(葉節點)
 */
public class MenuItem extends MenuComponent {

    private String name;
    private double price;
    
    public MenuItem(String name, double price) {
        this.name = name;
        this.price = price;
    }
    
    @Override
    public void addChild(MenuComponent component) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void removeChild(MenuComponent component) {
        throw new UnsupportedOperationException();
    }
    
    @Override
    public List<MenuComponent> getChilds() {
        return childs;
    }
    
    @Override
    public String getName() {
        return name;
    }
    
    @Override
    public double getPrice() {
        return price;
    }
    
    @Override
    public void print() {
        System.out.println("  " + getName() + ", " + getPrice());;
    }
}

(4)建立女招待

/**
 * 女招待
 */
public class Waitress {
    
    MenuComponent allMenus;
    
    public Waitress(MenuComponent allMenus) {
        this.allMenus = allMenus;
    }
    
    public void printMenu() {
        print(allMenus);
    }
    
    private void print(MenuComponent menuComponent) {
        menuComponent.print();
        for (MenuComponent child : menuComponent.getChilds()) {
            print(child);
        }
    }
}

(5)使用女招待打印菜單

public class Test {

    public static void main(String[] args) {
        // 全部菜單
        MenuComponent allMenus = new Menu("ALL MENUS");
        // 子菜單
        MenuComponent pancakeHouseMenu = new Menu("PANCAKE HOUSE MENU");
        MenuComponent dinerMenu = new Menu("DINER MENU");
        MenuComponent dessertMenu = new Menu("DESSERT MENU");
        
        // 添加煎餅屋菜單及菜單項
        allMenus.addChild(pancakeHouseMenu);
        pancakeHouseMenu.addChild(new MenuItem("Regular Pancake Breakfast", 2.99));
        pancakeHouseMenu.addChild(new MenuItem("Blueberry Pancakes", 3.49));
        pancakeHouseMenu.addChild(new MenuItem("Waffles", 3.59));
        // 添加餐廳菜單及菜單項
        allMenus.addChild(dinerMenu);
        dinerMenu.addChild(new MenuItem("BLT", 2.99));
        dinerMenu.addChild(new MenuItem("Soup of the day", 3.29));
        dinerMenu.addChild(new MenuItem("Hotdog", 3.05));
        // 添加甜點菜單及菜單項
        dinerMenu.addChild(dessertMenu);
        dessertMenu.addChild(new MenuItem("Apple Pie", 1.59));
        dessertMenu.addChild(new MenuItem("Cheesecake", 1.99));
        dessertMenu.addChild(new MenuItem("Sorbet", 1.89));
        
        // 使用女招待打印菜單
        Waitress waitress = new Waitress(allMenus);
        waitress.printMenu();
    }
}
相關文章
相關標籤/搜索