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


前言

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

組合模式

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

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

使用場景

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

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

要點

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

示例

下面我們通過一個具體實例來演示下組合模式到底是如何工作的。這里我們直接用了《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();

    }
運行結果

總結

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM