核心思想
- 组合模式(Composite Pattern)是一种结构型设计模式,用于将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式允许客户端以一致的方式对待单个对象和对象集合(容器),这样就可以用相同的方式对待单个对象(叶子节点)和由多个对象组成的集合(组合节点)。
结构
1. Component(抽象组件)
- 定义了叶子节点和复合节点的共同接口,可以是抽象类或接口。
2. Leaf(叶子节点)
- 在组合中表示单个对象,叶子节点没有子节点。
3. Composite(组合节点)
- 表示有子节点对象,是树形结构的父节点,负责管理自己的子节点。
现实世界类比
大部分国家的军队都采用层次结构管理。 每支部队包括几个师, 师由旅构成, 旅由团构成, 团可以继续划分为排。 最后, 每个排由一小队实实在在的士兵组成。 军事命令由最高层下达, 通过每个层级传递, 直到每位士兵都知道自己应该服从的命令。
组合模式分类
透明组合模式(Transparent Composite Pattern)和安全组合模式(Safe Composite Pattern)都是组合模式的变种,组合模式是结构型设计模式的一种,主要用来将对象组合成树形结构来表示“部分-整体”的层次结构。它让客户端可以统一对待单个对象和组合对象。
- 透明组合模式(标准实现) :在透明组合模式中,所有的组件类(即叶子节点和容器节点)都具有统一的接口。叶子节点和容器节点对外暴露相同的行为,客户端无需区分它们是单个对象还是组合对象。因此,客户端可以直接对组合对象进行操作,就像操作单个对象一样。
-
- 优点:简化客户端代码,不需要显式地判断节点是叶子节点还是容器节点。
- 缺点:对于复杂的树形结构,可能会导致组件类暴露过多的方法,违反了单一职责原则。
- 安全组合模式: 安全组合模式则更加注重树形结构的安全性,它通过接口或抽象类对叶子节点和容器节点进行了区分,通常容器节点和叶子节点的接口是不同的。容器节点通常拥有管理子节点的方法,而叶子节点则没有。这就要求客户端必须判断节点是叶子节点还是容器节点,从而选择不同的操作。
-
- 优点:设计更清晰,叶子节点和容器节点分开处理,避免暴露不必要的方法,符合单一职责原则。
- 缺点:客户端需要区分叶子节点和容器节点,代码会更复杂。
简单总结:
- 透明组合模式:叶子节点和容器节点对外有相同接口,客户端无需关心内部结构,代码简洁,但可能会暴露不必要的方法。
- 安全组合模式:叶子节点和容器节点有不同接口,客户端需要判断节点类型,代码结构更清晰,但会略显复杂。
适用场景
- 需要实现树状对象结构:组合模式提供了两种共享公共接口的基本元素类型:简单叶子节点和组合节点(容器),容器中可以包含叶子节点和其他容器,以便构建树状嵌套递归对象结构。
- 希望客户端代码以相同方式处理简单和复杂元素:组合模式中定义的所有元素共用同一个接口,因此客户端不必在意所使用对象的具体类。
优缺点
优点:
- 树形结构:可以利用多态和递归机制方便地使用复杂树结构。
- 透明性:客户端统一地处理叶子节点和组合节点,无需关心其之间的区别。
- 遵循开闭原则:方便地添加新叶子节点和组合节点,且不影响现有代码。
缺点:
- 设计复杂:对于简单的结构,组合模式可能引入不必要的复杂性。
- 过度抽象:由于组合模式将所有节点都抽象为
Component
类,可能会导致一些不必要的抽象,影响代码的可读性。
实现步骤
- 确保应用的核心模型能够以树状结构表示。 尝试将其分解为简单元素和容器。 记住, 容器必须能够同时包含简单元素和其他容器。
- 声明组件接口及其一系列方法, 这些方法对简单和复杂元素都有意义。
- 创建一个叶节点类表示简单元素。 程序中可以有多个不同的叶节点类。
- 创建一个容器类表示复杂元素。 在该类中, 创建一个数组成员变量来存储对于其子元素的引用。 该数组必须能够同时保存叶节点和容器, 因此请确保将其声明为组合接口类型。
实现组件接口方法时, 记住容器应该将大部分工作交给其子元素来完成。
- 最后, 在容器中定义添加和删除子元素的方法。
记住, 这些操作可在组件接口中声明。 这将会违反接口隔离原则, 因为叶节点类中的这些方法为空。 但是, 这可以让客户端无差别地访问所有元素, 即使是组成树状结构的元素。
示例
// 抽象组件——菜单组件
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 index){
throw new UnsupportedOperationException("不支持获取子菜单操作");
}
public String getName(){
return name;
}
public abstract void print();
}
// 叶子节点——菜单项
public class MenuItem extends MenuComponent{
public MenuItem(String name, int level) {
this.name = name;
this.level = level;
}
@Override
public void print() {
for (int i = 0; i < level; i++) {
System.out.print("--");
}
System.out.println("菜单项:" + name);
}
}
// 复合节点——菜单
public class Menu extends MenuComponent{
private List<MenuComponent> menuComponentList = new ArrayList<>();
public Menu(String name, int level) {
this.name = name;
this.level = level;
}
@Override
public void add(MenuComponent menuComponent) {
menuComponentList.add(menuComponent);
}
@Override
public void remove(MenuComponent menuComponent) {
menuComponentList.remove(menuComponent);
}
@Override
public MenuComponent getChild(int index) {
return menuComponentList.get(index);
}
@Override
public void print() {
for (int i = 0; i < level; i++) {
System.out.print("--");
}
System.out.println("菜单:" + name);
for (MenuComponent menuComponent : menuComponentList) {
menuComponent.print();
}
}
}
public class Client {
public static void main(String[] args) {
MenuComponent menu = new Menu("系统管理", 1);
MenuComponent menu1 = new Menu("菜单管理", 2);
menu1.add(new MenuItem("页面访问", 3));
menu1.add(new MenuItem("展开菜单", 3));
menu1.add(new MenuItem("编辑菜单", 3));
menu1.add(new MenuItem("删除菜单", 3));
menu1.add(new MenuItem("新增菜单", 3));
MenuComponent menu2 = new Menu("权限配置", 2);
menu2.add(new MenuItem("页面访问", 3));
menu2.add(new MenuItem("提交保存", 3));
MenuComponent menu3 = new Menu("角色管理", 2);
menu3.add(new MenuItem("页面访问", 3));
menu3.add(new MenuItem("新增角色", 3));
menu3.add(new MenuItem("修改角色", 3));
menu.add(menu1);
menu.add(menu2);
menu.add(menu3);
menu.print();
}
}
与其他模式的关系
- 桥接模式、 状态模式和策略模式 (在某种程度上包括适配器模式) 模式的接口非常相似。 实际上, 它们都基于组合模式——即将工作委派给其他对象, 不过也各自解决了不同的问题。 模式并不只是以特定方式组织代码的配方, 你还可以使用它们来和其他开发者讨论模式所解决的问题。
- 你可以在创建复杂组合树时使用生成器模式, 因为这可使其构造步骤以递归的方式运行。
- 责任链模式通常和组合模式结合使用。 在这种情况下, 叶组件接收到请求后, 可以将请求沿包含全体父组件的链一直传递至对象树的底部。
- 你可以使用迭代器模式来遍历组合树。
- 你可以使用访问者模式对整个组合树执行操作。
- 你可以使用享元模式实现组合树的共享叶节点以节省内存。
- 组合和装饰模式的结构图很相似, 因为两者都依赖递归组合来组织无限数量的对象。
- 装饰类似于组合, 但其只有一个子组件。 此外还有一个明显不同: 装饰为被封装对象添加了额外的职责, 组合仅对其子节点的结果进行了 “求和”。但是, 模式也可以相互合作: 你可以使用装饰来扩展组合树中特定对象的行为。
- 大量使用组合和装饰的设计通常可从对于原型模式的使用中获益。 你可以通过该模式来复制复杂结构, 而非从零开始重新构造。