《设计模式》组合模式
定义:
- 组合模式是一种结构型模式,又叫部分-整体模式,创建对象组的树形结构,将对象组合成树状结构以表示“整体-部分”的关系。
- 组合模式使得用户对单个对象和组合对象的访问具有一致性,即:组合能让客户以一致的方式处理个别对象以及组合对象。
组合模式主要包含三种角色:
抽象根节点(Component):定义系统各层次对象的公有方法和属性,可以预先定义一些默认行为和属性。
树枝节点(Composite):定义树枝节点的行为,存储子节点,组合树枝节点和叶子节点形成一个树形结构。
叶子节点(Leaf):叶子节点对象,其下再无分支,是系统层次遍历的最小单位。
组合模式的注意事项:
简化客户端操作:客户端只需要面对一致的对象而不用考虑整体部分或叶子节点的问题。
具有较强的扩展性:当需要更改组合对象时,只需要调整内部的层次关系,客户端不用做出任何改动。
方便创建出复杂的层次结构:客户端不用考虑组合内部的组成细节,方便添加节点或叶子从而创建出复杂的树形结构。
需要遍历组织机构或处理的对象具有树形结构时,非常适合使用组合模式。
要求较高的抽象性,如果节点和叶子有很多差异性,例如很多方法和属性都不一样,不适合使用组合模式。
组合模式的原理类图如下所示:
现在有一个需求,针对一个管理系统的菜单,打印出其包含的所有菜单以及菜单项(菜单
项是指不再包含其他内容的菜单条目)的名称,使用组合模式来实现这个案例。
类MenuComponent
/** * 菜单组件:不管是菜单还是菜单项都应该继承菜单组件 */ public abstract class MenuComponent { protected String name; protected int level; //添加菜单 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() { return name; } public void print() { throw new UnsupportedOperationException(); } }
MenuComponent 定义为抽象类,因为有一些共有的属性和行为要在该类中实现,Menu 和 MenuItem 类就可以只覆盖自己感兴趣的方法,而不用搭理不需要或者不感兴趣的方法。
Menu 类可以包含子菜单,因此需要覆盖 add()、remove()、getChild() 方法,但是 MenuItem 就不应该有这些方法。这里给出的默认实现是抛出异常,也可以根据自己的需要改写默认实现。
类Menu
public class Menu extends MenuComponent{ private List<MenuComponent> menuComponentList; public Menu(String name, int level) { this.level = level; this.name = name; menuComponentList = new ArrayList<>(); } @Override public void add(MenuComponent menuComponent) { menuComponentList.add(menuComponent); } @Override public void remove(MenuComponent menuComponent) { menuComponentList.remove(menuComponent); } @Override public MenuComponent getChild(int i) { return menuComponentList.get(i); } @Override public void print() { for (int i = 1; i < level; i++) { System.out.println("--"); } for (MenuComponent menuComponent : menuComponentList) { menuComponent.print(); } } }
Menu 类已经实现了除了 getName()
方法的其他所有方法,因为 Menu 类具有添加菜单、移除菜单和获取子菜单的功能。
类MenuItem
public class MenuItem extends MenuComponent{ public MenuItem(String name, int level) { this.level = level; this.name = name; } @Override public void print() { for (int i = 1; i < level; i++) { System.out.println("--"); } System.out.println(name); } }
MenuItem 是菜单项,不能再有子菜单,所以添加菜单、移除菜单和获取子菜单的功能并不用实现。
在使用组合模式时,根据抽象构件类的定义形式,可将组合模式分为透明组合模式和安全组合模式两种形式:
透明组合模式:在透明组合模式中,抽象根节点角色中声明了所有用于管理成员对象的方法,例如在示例中 MenuComponent 声明了 add() 、 remove() 方法,这样做的好处是确保所有的构件类都有相同的接口,透明组合模式也是组合模式的标准形式。
透明组合模式的缺点是不够安全:因为叶子对象和容器对象在本质上是有区别的,叶子对象不可能有下一个层次的对象,即不可能包含成员对象,因此为其提供 add()、remove() 等方法是没有意义的,这在编译阶段不会出错,但在运行阶段如果调用这些方法可能会出错(如果没有提供相应的错误处理代码)。
安全组合模式:在安全组合模式中,在抽象构件角色中没有声明任何用于管理成员对象的方法,而是在树枝节点 Menu 类中声明并实现这些方法。安全组合模式的缺点是不够透明,因为叶子构件和容器构具有不同的方法,且容器构件中那些用于管理成员对象的方法没有在抽象构件类中定义,因此客户端不能完全针对抽象编程,必须有区别地对待叶子构件和容器构件。
此外,组合模式在 JDK 源码中也有应用,集合类 HahsMap 就使用了组合模式: