Head First 设计模式 —— 11. 组合 (Composite) 模式

891 阅读3分钟

思考题

我们不仅仅要支持多个菜单,甚至还要支持菜单中的菜单。你如何处理这个新的设计需求? P355

  • 【提示】在我们的新设计中,真正需要以下三点: P354
    • 我们需要某种属性结构,可以容纳菜单、子菜单和菜单项
    • 我们需要确定能够在每个菜单的各个项之间游走,而且至少要像现在用迭代器一样方便
    • 我们也需要能够更有弹性地在菜单项之间游走。比方说,可能只需要遍历甜点菜单,或者可以遍历餐厅的整个菜单(包括甜点菜单在内)
  • 提供一个接口,统一菜单和菜单项的超类
    • 接口包含菜单和菜单项的共同方法,菜单中执行方法是依次执行每个子项中的相同方法
    • 接口包含菜单特有的增删改查子项的方法,菜单项中的增删改查子项的方法实现为直接抛出 UnsupportedOperationException
    • 可以通过 instanceof 判断当前项是菜单还是菜单项

组合模式

允许你将对象组合成树形结构来表现”整体/部分“层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。 组合模式

特点

  • 把相同的操作应用在组合和个别对象上,即可以忽略对象组合和个别对象之间的差别 P357
  • 以违反单一职责设计原则换取透明性,即不但要管理层次结构,还要同时包含管理组合和叶节点的操作,以将组合和叶节点一视同仁 P367

空迭代器:空对象(命令模式中提到过)的一个例子。空迭代器, hasNext() 永远返回 falsenext() 永远返回 null (个人觉得可以抛出 NoSuchElementException), remove() 永远抛出 UnsupportedOperationExceptionP372

思考题

public class Waitress {
    MenuComponent allMenus;
    
    public Waitress(MenuComponent allMenus) {
        this.allMenus = allMenus;
    }
    
    public void printMenu() {
        allMenus.print();
    }
    
    public void printVegetarianMenu() {
        Iterator iterator = allMenus.createIterator();
        System.out.println("\nVEGETARIAN MENU\n----");
        while (iterator.hasNext()) {
            MenuComponent menuComponent = (MenuComponent)iterator.next();
            try {
                if (menuComponent.isVegetarian()) {
                    menuComponent.print();
                }
            } catch (UnsupportedOperationException e) {}
        }
    }
}

printVegetarianMenu() 方法中只有菜单项的 print() 方法可以被调用,绝对不能调用菜单(组合)的 print() 方法。你能说出原因吗? P373

  • 使用迭代器遍历时已经会遍历所有的节点(包括组合节点和叶节点),而组合节点会打印所有子节点信息,如果调用组合节点的 print() ,则必定会重复打印某些叶节点。

思考题

配对下列模式和描述: P379
策略模式:封装可互换的行为,并使用委托决定使用哪一个
适配器模式:改变一个或多个类的接口
迭代器模式:提供一个方式来遍历集合,而无须暴露集合的实现
外观模式:简化一群类的接口
组合模式:客户可以将对象的集合以及个别的对象一视同仁
观察者模式:当某个状态改变时,允许一群对象能被通知到

所思所想

  • 以前在很多地方其实都用到了这种思想,二叉树、trie 树和线段树等树结构都允许将节点组成树形结构来表现”整体/部分“的层次结构,并且提供了一致的方式处理非叶节点和叶节点(非叶节点的方法内部按照特定逻辑处理和调用子节点的相同方法)

本文首发于公众号:满赋诸机(点击查看原文) 开源在 GitHub :reading-notes/head-first-design-patterns