写在前面的话
复习、总结23种设计模式
上一篇
组合模式
记重点
只要是树形结构,就可以考虑使用组合模式(如树形菜单,文件、文件夹的管理)。
- 维护和展示部分-整体关系的场景, 如树形菜单、 文件和文件夹管理。
- 从一个整体中能够独立出部分模块或功能的场景
什么是组合模式(合成模式、部分-整体模式)
将对象组合成树形结构以表示“部分-整体”的层次结构, 使得用户对单个对象和组合对象的使用具有一致性。(用于处理树形结构数据)
- Component 抽象构件角色
定义参加组合对象的共有方法和属性,可以定义一些默认的行为或属性。
- Leaf 叶子节点
叶子对象,其下再也没有其他的子节点,是遍历的最小单位。
- Composite 树枝构件
树枝对象,作用是组合树枝节点和叶子节点形成一个树形结构。
组合模式通用代码实现
Component抽象构件角色,定义参加组合对象的共有方法和属性,可以定义一些默认的行为或属性
package com.design.pattern.composite.test01;
// Component 抽象构件角色
//定义参加组合对象的共有方法和属性,可以定义一些默认的行为或属性
public abstract class Component {
// 个体和整体都有的共享方法
public void doSth() {
}
}
Composite树枝构件,树枝对象,作用是组合树枝节点和叶子节点形成一个树形结构
package com.design.pattern.composite.test01;
import java.util.ArrayList;
import java.util.List;
// 组合模式的重点
// Composite 树枝构件
//树枝对象,作用是组合树枝节点和叶子节点形成一个树形结构
public class Composite extends Component {
private List<Component> componentList = new ArrayList<>();
// 可以增加一个叶子节点或者树枝构件
public void add(Component component) {
componentList.add(component);
}
public void remove(Component component) {
componentList.remove(component);
}
public List<Component> getChildren() {
return componentList;
}
}
Leaf叶子节点,叶子对象,其下再也没有其他的子节点,是遍历的最小单位
package com.design.pattern.composite.test01;
// Leaf叶子节点
// 叶子对象,其下再也没有其他的子节点,是遍历的最小单位
public class Leaf extends Component {
@Override
public void doSth() {
System.out.println("叶子节点逻辑");
}
}
测试类
package com.design.pattern.composite.test01;
public class Client {
public static void main(String[] args) {
// 创建一个根节点
Composite root = new Composite();
root.doSth();
// 创建一个树枝节点
Composite branch = new Composite();
// 创建一个叶子节点
Composite leaf = new Composite();
// 串联起来
root.add(branch);
branch.add(leaf);
// 遍历
display(root);
}
private static void display(Composite root) {
for (Component child : root.getChildren()) {
if (child instanceof Leaf) {
child.doSth();
} else {
display((Composite) child);
}
}
}
}
以一个经典笔试题为例请打印显示文件夹结构的功能, 按如下格式显示
-
我是根目录
-
我是二级目录
- 我是文件
-
抽象构件(Component):文件夹和文件统一为文件
package com.design.pattern.composite.test02;
public abstract class FileComponent {
//文件名称
protected String name;
//文件的层级 1 一级目录 2 二级目录 ...
protected Integer level;
//文件的类型 1 文件夹 2文件
protected Integer type;
//添加子文件/文件夹
public abstract void add(FileComponent fileComponent);
//移除子文件/文件夹
public abstract void remove(FileComponent fileComponent);
//获取指定的子文件/文件夹
public abstract FileComponent getChild(int index);
//打印子 子文件/子文件夹 名称的方法
public abstract void print();
}
树枝构件(Composite):文件夹
package com.design.pattern.composite.test02;
import java.util.ArrayList;
import java.util.List;
public class FileFolder extends FileComponent {
//文件夹可以有多个子文件夹或者子文件
private List<FileComponent> fileComponentList;
public FileFolder(String name, Integer level, Integer type) {
this.name = name;
this.level = level;
this.type = type;
this.fileComponentList = new ArrayList<>();
}
@Override
public void add(FileComponent fileComponent) {
fileComponentList.add(fileComponent);
}
@Override
public void remove(FileComponent fileComponent) {
fileComponentList.remove(fileComponent);
}
@Override
public FileComponent getChild(int index) {
return fileComponentList.get(index);
}
@Override
public void print() {
//打印菜单名称
for (int i = 0; i < level; i++) {
System.out.print("\t");
}
System.out.println(name);
//打印子菜单或者子菜单项名称
for (FileComponent component : fileComponentList) {
component.print();
}
}
}
树叶构件(Leaf):文件
package com.design.pattern.composite.test02;
public class FileItem extends FileComponent {
public FileItem(String name, Integer level, Integer type) {
this.name = name;
this.level = level;
this.type = type;
}
@Override
public void add(FileComponent fileComponent) {
}
@Override
public void remove(FileComponent fileComponent) {
}
@Override
public FileComponent getChild(int index) {
return null;
}
@Override
public void print() {
//打印文件的名称
for (int i = 0; i < level; i++) {
System.out.print("\t");
}
System.out.println(name);
}
}
遍历文件夹
package com.design.pattern.composite.test02;
public class Test {
public static void main(String[] args) {
//定义根目录
FileComponent rootComponent = new FileFolder("我是根目录", 1, 1);
//定义二级文件夹
FileComponent secondLevelComponent = new FileFolder("我是二级目录", 2, 1);
//定义文件
FileComponent file = new FileItem("我是文件", 3, 2);
//向根目录添加二级目录
rootComponent.add(secondLevelComponent);
//向二级目录添加文件
secondLevelComponent.add(file);
//打印
rootComponent.print();
}
}
组合模式的优点
高层模块调用简单
一棵树形机构中的所有节点都是Component,局部和整体对调用者来说没有任何区别,也就是说,高层模块不必关心自己处理的是单个对象还是整个组合结构,简化了高层模块的代码。
节点自由增加
使用了组合模式后,我们可以看看,如果想增加一个树枝节点、树叶节点是不是都很容易只要找到它的父节点就成,非常容易扩展,符合开闭原则,对以后的维护非常有利。
组合模式的缺点
组合模式有一个非常明显的缺点,看到我们在场景类中的定义,提到树叶和树枝使用时的定义了吗?直接使用了实现类!这在面向接口编程上是很不恰当的,与依赖倒置原则冲突,读者在使用的时候要考虑清楚,它限制了你接口的影响范围。
- 在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则
- 设计较复杂,客户端需要花更多时间理清类之间的层次关系。
- 不容易限制容器中的构件。