组合模式.

一、概念

  • 组合模式:允许你将对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。
  • 组合包含组件。组件有两种:组合和叶节点元素。组合持有一群孩子,这些孩子可以是别的组合或者叶节点元素。
  • 角色:
     组合部件(Component):它是一个抽象角色,为要组合的对象提供统一的接口。
     叶子节点(Leaf):定义无子节点的行为,在组合中表示叶子节点对象,叶子节点不能有子节点。
     组合节点(Composite):定义有子节点的行为,用来存储部件,实现在Component接口中的有关操作,如增加(Add)和删除(Remove)。

avatar

二、Demo 实现

TOPIC:我们的任务是想实现一个树形结构的菜单,如下~
avatar

1、组合部件

/**
 * @Description: 组件的抽象类,菜单组件的角色是为叶节点和组合节点提供一个公共的接口。
 * @author: cuixiuyin
 * @date: 2018/12/29 08:59
 */
public abstract class MenuComponent {

    /**
     * @Description 组合节点(菜单)的方法组织在一起
     * @author cuixiuyin
     * @date 2018/12/29 09:03
     */
    public void add(MenuComponent menuComponent) {
        throw new UnsupportedOperationException();
    }

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

    public MenuComponent getChild(int i) {
        throw new UnsupportedOperationException();
    }

    /**
     * @Description 叶子节点(菜单项)的方法组织在一起、其中有一些也可以用于组合节点(菜单)上
     * @author cuixiuyin
     * @date 2018/12/29 09:03
     */
    public String getName() {
        throw new UnsupportedOperationException();
    }

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

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

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

    /**
     * @Description 相关操作
     * @author cuixiuyin
     * @date 2019/01/02 09:04
     */
    public void print() {
        throw new UnsupportedOperationException();
    }
}

组合部件为叶子节点和组合节点定义了统一的接口。所有的操作,如果子类没有实现,我们默认抛出一个 UnsupportedOperationException 异常。

为了要保持透明性,组合内所有的对象都必须实现这个接口,否则客户就必须操心哪个对象是用哪个接口,这就失去了组合模式的意义。

2、叶子节点

/**
 * @Description: 菜单项 (叶子节点)
 * @author: cuixiuyin
 * @date: 2018/12/29 09:09
 */
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.print(getName() + "--" + getDescription() + "," + getPrice() + ",");
        if (isVegetarian()) {
            System.out.print("(v)");
        }
        System.out.println();
    }
}

叶子节点没有子节点,只有一些菜单项上的类目,不存在下一级结构的维护。

3、组合节点

/**
 * @Description: 菜单(组合节点)
 * @author: cuixiuyin
 * @date: 2018/12/29 09:19
 */
public class Menu extends MenuComponent {
    /**
     * 可能持有菜单项或其他菜单
     */
    List<MenuComponent> list = new ArrayList<>();

    /**
     * 名称
     */
    private String name;
    /**
     * 描述
     */
    private String description;

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

    @Override
    public void add(MenuComponent menuComponent) {
        list.add(menuComponent);
    }

    @Override
    public void remove(MenuComponent menuComponent) {
        list.remove(menuComponent);
    }

    @Override
    public MenuComponent getChild(int i) {
        return list.get(i);
    }

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

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

    /**
     * @Description 递归 —— 不仅打印出菜单本身的信息,也打印出菜单内所有组件的内容:其他菜单和菜单项目。
     * @author cuixiuyin
     * @date 2018/12/29 09:26
     */
    @Override
    public void print() {
        System.out.println("\n" + getName() + "," + getDescription());
        System.out.println("--------------------");
        Iterator<MenuComponent> iterator = list.iterator();
        while (iterator.hasNext()) {
            MenuComponent component = iterator.next();
            component.print();
        }
    }
}

组合节点维护了一个集合,这个集合可以持有其他菜单项(叶子节点)或者菜单(组合节点)。

4、测试

public class Waitress {

    private MenuComponent menuComponent;

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

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

public class Test {

    public static void main(String[] args) {
        // 1、创建所有的菜单对象
        MenuComponent breakfast = new Menu("早餐菜单", "Breakfast");
        MenuComponent lunch = new Menu("午餐菜单", "Lunch");
        MenuComponent dinner = new Menu("晚餐菜单", "Dinner");
        MenuComponent dessert = new Menu("晚餐甜点", "Dessert");

        // 2、创建顶层菜单
        MenuComponent top = new Menu("顶层菜单", "Top");
        top.add(breakfast);
        top.add(lunch);
        top.add(dinner);

        // 3、晚餐菜单中加入甜点
        dinner.add(dessert);

        // 4、构建每份菜单中的菜单项(叶子节点)
        breakfast.add(new MenuItem("dumplings", "饺子", false, 10.0));
        breakfast.add(new MenuItem("bread", "面包", true, 7.0));
        breakfast.add(new MenuItem("mile", "牛奶", false, 5.0));

        lunch.add(new MenuItem("rice", "米饭", true, 2.0));
        lunch.add(new MenuItem("burger", "汉堡", false, 6.0));
        lunch.add(new MenuItem("vegetables", "蔬菜", true, 1.0));

        dinner.add(new MenuItem("noodle", "面条", true, 4.0));

        dessert.add(new MenuItem("cake", "蛋糕", true, 3.0));

        // 5、把最顶层的组合给服务员,以便可以获取到所有订单
        Waitress waitress = new Waitress(top);
        waitress.printMenu();
    }
}

avatar

5、组合迭代器 — 组合模式和迭代器模式的配合

/**
 * @Description: 组合迭代器,遍历整个树结构
 * @author: cuixiuyin
 * @date: 2018/12/29 21:30
 */
public class CompositeIterator implements Iterator {
    private Stack<Iterator> stack = new Stack();

    public CompositeIterator(Iterator iterator) {
        stack.push(iterator);
    }

    @Override
    public boolean hasNext() {
        if (stack.empty()) {
            return false;
        }
        Iterator peek = stack.peek();
        if (peek.hasNext()) {
            return true;
        }
        stack.pop();
        return hasNext();
    }

    @Override
    public MenuComponent next() {
        if (hasNext()) {
            Iterator peek = stack.peek();
            MenuComponent component = (MenuComponent) peek.next();
            if (component instanceof Menu) {
                stack.push(component.createIterator());
            }
            return component;
        }
        return null;
    }
}

该组合迭代器,从选定的节点开始,往下遍历整个树形结构,选取符合条件的数据。亲测,该迭代器只适合做筛选的操作,不适合做整体的操作,因为会有重复的步骤。

三、总结

  • 迭代器模式:提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。
  • 迭代器模式的另一个意义在于:把游走的任务放在迭代器上,而不是聚合上。这样简化了聚合的接口和实现,也让责任各得其所。
  • 单一职责原则:一个类应该只有一个引起变化的原因。类的每个责任都有改变的潜在区域,超过一个责任,意味着超过一个改变的区域。因此,尽量让每个类保持单一责任。
  • 单一职责原则听起来很容易,但其实做起来并不简单:区分设计中的责任,是最困难的事情之一。我们的大脑很习惯看着一大群的行为,然后将它们集中在一起,尽管他们可能属于两个或多个不同的责任。想要成功的唯一方法,就是努力不懈地检查你的设计,随着系统的成长。随时观察有没有迹象显示某个类改变的原因超过一个。

  • 组合模式特别适用于树形结构,假设我们有了一个树形结构的菜单、子菜单和可能还带有菜单项的子菜单,那么任何一个菜单都是一种“组合”。因为它既可以包含其他菜单,也可以包含菜单项,甚至个别对象只是菜单项 —— 并未持有其他对象。
  • 组合模式是违反单一职责原则的,这是一个典型的折衷案例。组合模式以单一责任设计原则换取换取透明性。什么是透明性?通过让组件的接口同时包含一些管理子节点和叶节点的操作,客户就可以将组合和叶节点一视同仁。也就是说,一个元素究竟是组合还是叶节点,对客户是透明的。为了维护透明性,每一个对象都使有相同的接口,难免组合中有些对象的行为不太一样,我们可以选择返回 null 或者 false,甚至是直接抛出异常。
  • 组合模式的优点:
     1、组合模式让我们能用树形方式创建对象的结构,树里面包含了组合以及个别的对象(叶节点元素)。
     2、使用组合结构,我们能把相同的操作应用在组合和个别对象(叶节点元素)上。换句话说,在大多数情况下,我们可以忽略对象组合和个体对象(叶节点元素)之间的差别。
  • 组合模式的使用场景:
     1、当想表达对象的“整体/部分”的层次结构时。
     2、希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象时。
  • 在实现组合模式时,有许多设计上的折衷。你要根据需要平衡透明性和安全性。
posted @ 2019-01-02 13:14  JMCui  阅读(445)  评论(0编辑  收藏  举报