1. 程式人生 > 其它 >設計模式詳解——組合模式

設計模式詳解——組合模式

前言

今天我們分享的這個設計模式,用一句話來概括的話,就是化零為整,再進一步解釋就是,通過這個設計模式,我們可以像操作一個物件一樣操作一個物件的集合,不過這個物件在組合模式中被稱作葉節點,而物件的集合被稱為組合,而這個結合本身也是也節點的樹形結構的集合。是不是感覺越來越繞了呢?沒關係,下面我就來詳細看下組合模式的基本原理和具體實現。

組合模式

組合模式允許我們將物件組合成樹形結構來表現“整體/部分”層次結構。組合能讓客戶以一致的方式處理個別對象以及物件組合。

它在我們樹型結構的問題中,模糊了簡單元素和複雜元素的概念,客戶程式可以像處理簡單元素一樣來處理複雜元素,從而使得客戶程式與複雜元素的內部結構解耦。

使用場景

  • 想表示物件的部分-整體層次結構(樹形結構)。

  • 希望使用者忽略組合物件與單個物件的不同,使用者將統一地使用組合結構中的所有物件。

要點

  • 組合模式讓我們能用樹形方式建立物件的結構,樹裡面包含了組合以及個別的物件
  • 使用組合模式,我們能把相同的操作應用到組合和個別物件上。換句話說,在大多數情況下,我們可以忽略物件組合和個別物件之間的差別。

示例

下面我們通過一個具體例項來演示下組合模式到底是如何工作的。這裡我們直接用了《Head First設計模式》上的示例,是對餐廳選單的模擬,只不過我引入了一個通用介面。

組合介面

首先是組合的介面,這個介面不論是葉節點還是葉節點組合都需要繼承,不過都不是直接繼承。

public interface Component {
    void add(Component component);
    void remove(Component component);
    Component getChild(int i);
}
組合抽象類

這個抽象類就是給葉節點和節點組合繼承的,其中方法都有了預設實現,預設都丟擲了UnsupportedOperationException

public abstract class MenuComponent implements Component {
    @Override
    public void add(Component component) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void remove(Component component) {
        throw new UnsupportedOperationException();
    }

    @Override
    public Component getChild(int i) {
        throw new UnsupportedOperationException();
    }

    public String getName() {
        throw new UnsupportedOperationException();
    }

    public String getDescription() {
        throw new UnsupportedOperationException();
    }

    public double getPrice() {
        throw new UnsupportedOperationException();
    }

    public boolean isVegetarian() {
        throw new UnsupportedOperationException();
    }

    public void print() {
        throw new UnsupportedOperationException();
    }

}
葉節點實現

這裡的葉節點主要覆寫了父類的getNamegetDescriptionisVegetariangetPrice等方法,這些方法也主要是針對具體選單的

public class MenuItem extends MenuComponent {
    private String name;
    private String description;
    private boolean vegetarian;
    private double price;

    public MenuItem(String name, String description, boolean vegetarian, double price) {
        this.name = name;
        this.description = description;
        this.vegetarian = vegetarian;
        this.price = price;
    }

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

    @Override
    public String getDescription() {
        return description;
    }

    @Override
    public boolean isVegetarian() {
        return vegetarian;
    }

    @Override
    public double getPrice() {
        return price;
    }

    @Override
    public void print() {
        System.out.println("==========start=============");
        System.out.printf("name: %s  price: ¥%s%n", this.getName(), this.getPrice());
        System.out.printf("description: %s  isVegetarian: %s%n", this.getDescription(), this.isVegetarian());
        System.out.println("==========end=============");
    }
}
節點組合實現

因為節點組合要管理葉節點,所以這裡主要實現了addremovegetChild等方法,當然也實現了getNamegetDescription等基礎方法:

public class Menu extends MenuComponent {
    ArrayList<Component> menuComponents = new ArrayList<>();
    String name;
    String description;

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

    @Override
    public void add(Component component) {
        menuComponents.add(component);
    }

    @Override
    public void remove(Component component) {
        menuComponents.remove(component);
    }

    @Override
    public Component getChild(int i) {
        return menuComponents.get(i);
    }

    @Override
    public String getName() {
        return super.getName();
    }

    @Override
    public String getDescription() {
        return super.getDescription();
    }

    @Override
    public void print() {
        System.out.println("==========start=============");
        System.out.printf("name: %s", this.getName());
        System.out.printf("description: %s", this.getDescription());
        System.out.println("==========child start=============");
        Iterator<Component> iterator = menuComponents.iterator();
        while (iterator.hasNext()) {
            MenuComponent component = (MenuComponent)iterator.next();
            component.print();
        }
        System.out.println("==========child end=============");
        System.out.println("============end===========");
    }
}
測試程式碼

下面我們開始編寫測試程式碼。這裡我們分別構造了多個葉節點,並將其中一部分組成節點組合,最後分別執行葉節點和子節點的print方法(這裡的print方法其實就是我們設計模式原理圖中的operation方法)

    @Test
    public void testComponent() {

        // 葉節點
        MenuItem slr = new MenuItem("燒鹿茸", "好吃美味,價格實惠", Boolean.FALSE, 180.0);
        MenuItem sxz = new MenuItem("燒熊掌", "好吃美味,價格實惠", Boolean.FALSE, 190.0);

        MenuItem hsr = new MenuItem("紅燒肉", "好吃美味,價格實惠", Boolean.FALSE, 36.0);
        MenuItem hsqz = new MenuItem("紅燒茄子", "好吃美味,價格實惠", Boolean.TRUE, 14.0);
        MenuItem hsjk = new MenuItem("紅燒雞塊", "好吃美味,價格實惠", Boolean.FALSE, 38.0);
        MenuItem yxrs = new MenuItem("魚香肉絲", "好吃美味,價格實惠", Boolean.FALSE, 22.0);
        MenuItem ssbc = new MenuItem("手撕包菜", "好吃美味,價格實惠", Boolean.TRUE, 12.0);

        // 組合節點
        Menu menu = new Menu("家常菜", "美味家常菜");
        menu.add(hsr);
        menu.add(hsqz);
        menu.add(hsjk);
        menu.add(yxrs);
        menu.add(ssbc);
        // 子節點print方法
        slr.print();
        sxz.print();
        // 組合節點print方法
        menu.print();

    }
執行結果

總結

想必通過上面的示例,各位小夥伴已經對組合模式有了一定的認知,對這種設計模式適用的場景也有了比較明確認識:如果在一個業務中,單個物件和物件的集合需要具備同樣的型別和方法,那組合模式就是最佳選擇,比如我們這裡的選單和選單組合,當然,更具體的應用場景,還需要各位小夥伴結合具體應用場景分析,但是學習的時候多思考應用場景才是學習正在的目的。好了,今天就到這裡吧!