1、定义
将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
2、模式结构
组合模式由三部分组成:
- Component(抽象构件):它可以是接口或抽象类,为叶子构件和容器构件对象声明接口,在该角色中可以包含所有子类共有行为的声明和实现。在抽象构件中定义了访问及管理它的子构件的方法,如增加子构件、删除子构件、获取子构件等。
- Leaf(叶子构件):它在组合结构中表示叶子节点对象,叶子节点没有子节点,它实现了在抽象构件中定义的行为。对于那些访问及管理子构件的方法,可以通过异常等方式进行处理。
- Conposite(容器构件):它在组合结构中表示容器节点对象,容器节点包含子节点,其子节点可以是叶子节点,也可以是容器节点,它提供一个集合用于存储子节点,实现了在抽象构件中定义的行为,包括那些访问及管理子构件的方法,在其业务方法中可以递归调用其子节点的业务方法。
3、实例
3.1 Component(抽象构件)
public abstract class Component {
public String getName() {
throw new UnsupportedOperationException("不支持获取名称操作");
}
public void add(Component component) {
throw new UnsupportedOperationException("不支持添加操作");
}
public void remove(Component component) {
throw new UnsupportedOperationException("不支持删除操作");
}
public void print() {
throw new UnsupportedOperationException("不支持打印操作");
}
public String getContent() {
throw new UnsupportedOperationException("不支持获取内容操作");
}
}
3.2 文件夹类Folder(容器构件)
public class Folder extends Component {
private String name;
private List<Component> componentList = new ArrayList<>();
private int level;
public Folder(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public void add(Component component) {
componentList.add(component);
}
@Override
public void remove(Component component) {
componentList.remove(component);
}
@Override
public void print() {
System.out.println(getName());
if (level == 0) {
level = 1;
}
String prefix = "";
for (int i = 0; i < level; i++) {
prefix += "\t- ";
}
for (Component component : componentList) {
if (component instanceof Folder) {
((Folder)component).level = level + 1;
}
System.out.print(prefix);
component.print();
}
level = 0;
}
}
3.3 文件类File(叶子构件)
public class File extends Component {
private String name;
private String content;
public File(String name, String content) {
this.name = name;
this.content = content;
}
@Override
public String getName() {
return name;
}
@Override
public void print() {
System.out.println(getName());
}
@Override
public String getContent() {
return content;
}
}
3.4 客户端调用
public class Client {
public static void main(String[] args) {
Folder DSFolder = new Folder("设计模式资料");
File note1 = new File("组合模式.md", "组合模式组合多个对象形成树形结构以表示具有\"整体-部分\"关系的层次结构");
File note2 = new File("工厂方法模式.md", "工厂方法模式定义一个用于创建对象的接口,让子类决定将哪一个类实例化。");
DSFolder.add(note1);
DSFolder.add(note2);
Folder codeFolder = new Folder("样例代码");
File readme = new File("README.md", "# 设计模式示例代码项目");
Folder srcFolder = new Folder("src");
File code = new File("组合模式示例.java", "这是组合模式的示例代码");
srcFolder.add(code);
codeFolder.add(readme);
codeFolder.add(srcFolder);
DSFolder.add(codeFolder);
DSFolder.print();
}
}
4、适用场景
- 在具有整体和部分的层次结构中,希望通过一种方式忽略整体与部分的差异,客户端可以一致地对待它们。
- 在一个使用面向对象开发的系统中需要处理一个树形结构。
- 在一个系统中能够分离叶子对象和容器对象,而且它们的类型不固定,需要增加一些新的类型。
5、在JDK集合的应用
6、优缺点
6.1 优点
- 可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,它让客户端忽略了层次的差异,方便对整个层次结构进行控制。
- 客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端代码。
- 增加新的容器构件和叶子构件都很方便,无须对现有类库进行任何修改,符合“开闭原则”。
- 为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子对象和容器对象的递归组合,可以形成复制的树形结构,但对树形结构的控制却非常简单。
6.2 缺点
- 使得设计更加复杂,客户端需要花更多时间理清类之间的层次关系。
- 增加新构件时很难对容器中的构件类型进行限制。
7、安全方式VS透明方式
- 安全方式在抽象组件中只定义一些默认的行为或属性,它是把容器节点和叶子节点彻底分开;透明方式是把用来组合使用的方法放到抽象类中,不管叶子对象和容器对象都有相同的结构,通过判断确定是叶子节点还是容器节点,如果处理不当,这个会在运行期出现问题,不是很建议的方式。
- 安全方式与依赖倒置原则冲突;透明方式的好处就是它基本遵循了依赖倒置原则,方便系统进行扩展。
- 安全方式在遍历树形结构的时候需要进行强制类型转换;在透明方式下,遍历整个树形结构是比较容易的,不用进行强制类型转换。