【设计形式】之组合模式(Composite)

【设计模式】之组合模式(Composite)

组合模式的定义为:把对象组合成属性结构来表示局部-整体的架构。组合模式使得客户端可以统一第处理单个对象和对象组合。

英文定义:Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.


由定义可以看出,组合模式有三个关键词。

第一个是tree structures:组合模式的数据结构是一颗多叉树。

第二个是part-whole:这个其实也是树的特点,类似于分形图形局部自相似性的特点。

第三个是uniformly:正式因为前两个特点,所以使用组合模式的时候是无需关心到底是处理地是字节点还是父几点。


组合模式的关键就是一个表示即可以表示节点又可以表示父节点的基类。

The key to composite pattern is an abstract class that represents both primitives and their containers.

因为基类包含了叶子节点跟非叶子节点(类似于一个容器)的操作,因此这个基类就不可避免地定义了一些子类不需要的操作,进而可能产生不安操作。

所以就违背了OO设计的一个原则:

A class should only define operations that are meaningful to its subclasses.


软件设计一个很重要的问题就是要衡量每个模式的trade-off,而组合模式的一个优势就是可以统一地处理叶子节点跟非叶子节点,因此组合模型强调的是透明性(transparency)大于安全性(safety)。


下例说明了组合模式的使用方法。

首先定义所有组件的基类

public abstract class MenuComponent {
    public void add(MenuComponent menuComponent) {
        throw new UnsupportedOperationException();
    }

    public void remove(MenuComponent menuComponent) {
        throw new UnsupportedOperationException();
    }

    public MenuComponent 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();
    }
}

叶子节点

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

    public String getName() {
        return name;
    }

    public String getDescription() {
        return description;
    }

    public double getPrice() {
        return price;
    }

    public boolean isVegetarian() {
        return vegetarian;
    }

    public void print() {
        System.out.println(" " + getName());
        if (isVegetarian()) {
            System.out.print("(v)");
        }

        System.out.println(", " + getPrice());
        System.out.println("    -- " + getDescription());
    }
}

非叶子节点,容器

import java.util.ArrayList;
import java.util.Iterator;

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

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

    public void add(MenuComponent menuComponent) {
        menuComponents.add(menuComponent);
    }

    public void remove(MenuComponent menuComponent) {
        menuComponents.remove(menuComponent);
    }

    public MenuComponent getChild(int i) {
        return (MenuComponent)menuComponents.get(i);
    }

    public String getName() {
        return name;
    }

    public String getDescription() {
        return description;
    }

    public void print() {
        System.out.println("\n" + getName());
        System.out.println(", " + getDescription());
        System.out.println("---------------------");

        Iterator iterator = menuComponents.iterator();
        while (iterator.hasNext()) {
            MenuComponent menuComponent = 
                (MenuComponent)iterator.next();
            menuComponent.print();
        }
    }
}

客户端,使用者

public class Waitress {
    MenuComponent allMenus;

    public Waitress(MenuComponent allMenus) {
        this.allMenus = allMenus;
    }

    public void printMenu() {
        allMenus.print();
    }
}


测试代码

public class MenuTestDrive {
    public static void main(String[] args) {
        MenuComponent pancakeHouseMenu = 
            new Menu("PANCAKE HOUSE MENU", "Breakfast");
        MenuComponent dinerMenu = 
            new Menu("DINER MENU", "Lunch");
        MenuComponent cafeMenu = 
            new Menu("CAFE MENU", "Dinner");
        MenuComponent dessertMenu = 
            new Menu("DESSERT MENU", "Dessert of course!");

        MenuComponent allMenus = new Menu("ALL MENUS", "All menus combined");

        allMenus.add(pancakeHouseMenu);
        allMenus.add(dinerMenu);
        allMenus.add(cafeMenu);

        // add menu items here

        dinerMenu.add(new MenuItem(
            "Pasta",
            "Spaghetti with Marinara Sauce, and a slice of sourdough bread",
            true,
            3.89));
        dinerMenu.add(dessertMenu);

        dessertMenu.add(new MenuItem(
            "Apple Pie",
            "Apple pie with a flakey crust, topped with vanilla icecream",
            true,
            1.59));

        // add more menu items here

        Waitress waitress = new Waitress(allMenus);

        waitress.printMenu();
    }
}

以上便是组合模式的一个实例,比较简单。

其实组合模式也是比较简单的一个模式,关键还是要依赖与需要解决问题的数据结构。