组合模式介绍
组合模式(Composite Pattern)也称为部分整体模式(Part-Whole-Pattern),结构型设计模式之一,它将一组相似的对象看作一个对象处理,并根据一个树状结构来组合对象,然后提供一个统一的方法去访问相应的对象,以此忽略掉对象与对象集合之间的差别。
组合模式的定义
将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
组合模式的使用场景
表示对象的部分-整体层次结构时。
从一个整体中能够独立出部分模块或功能的场景。
组合模式的UML类图
根据类图我们可以得出如下一个组合模式的通用模式代码。
package com.guifa.patterndemo.compositepattern;
public abstract class Component {
// 节点名
protected String name;
public Component(String name) {
this.name = name;
}
// 具体的逻辑方法由子类实现
public abstract void doSomething();
}
package com.guifa.patterndemo.compositepattern;
import java.util.ArrayList;
import java.util.List;
public class Composite extends Component {
private List<Component> components = new ArrayList<>();
public Composite(String name) {
super(name);
}
@Override
public void doSomething() {
System.out.println(name);
if (null != components) {
for (Component c : components) {
c.doSomething();
}
}
}
/**
* 添加子节点
*
* @param child 子节点
*/
public void addChild(Component child) {
components.add(child);
}
/**
* 移除子节点
*
* @param child 子节点
*/
public void removeChild(Component child) {
components.remove(child);
}
/**
* 获取子节点
*
* @param index 子节点对应下标
*
* @return 子节点
*/
public Component getChildren(int index) {
return components.get(index);
}
}
package com.guifa.patterndemo.compositepattern;
public class Leaf extends Component {
public Leaf(String name) {
super(name);
}
@Override
public void doSomething() {
System.out.println(name);
}
}
package com.guifa.patterndemo.compositepattern;
public class Client {
public static void main(String[] args) {
// 构造一个根节点
Composite root = new Composite("Root");
// 构造两个枝干节点
Composite branch1 = new Composite("Branch1");
Composite branch2 = new Composite("Branch2");
// 构造两个叶子节点
Leaf leaf1 = new Leaf("Leaf1");
Leaf leaf2 = new Leaf("Leaf2");
// 将叶子节点添加至枝干节点中
branch1.addChild(leaf1);
branch2.addChild(leaf2);
// 将枝干节点添加至根节点中
root.addChild(branch1);
root.addChild(branch2);
// 执行方法
root.doSomething();
}
}
输出结果:
Root
Branch1
Leaf1
Branch2
Leaf2
Process finished with exit code 0
角色介绍:
- Component:抽象根节点,为组合中的对象声明接口。在适当的情况下,实现所有类共有接口的缺省行为。声明一个接口用于访问和管理Component的子节点。可以在递归结构中定义一个接口,用于访问一个父节点,并在合适的情况下实现它。
- Composite:定义子节点的那些枝干节点的行为,存储子节点,在Component接口中实现与子节点有关的操作。
- Leaf:在组合中表示叶子节点对象,叶子节点没有子节点,在组合中定义节点对象的行为。
- Client:通过Component接口操纵组合节点对象。
上述例子中我们在Client客户类中直接使用了Component的实现类,违反了依赖倒置原则。我们稍微修改下上面的类图将位于Composite中的一些实现方法定义到Component中,那么我们会得到一个不一样的组合模式,如图:
像这样将组合所使用的方法定义在抽象类的方式称为透明的组合模式,而上面我们所说的组合模式则称为安全的组合模式。透明组合模式中不管是叶子节点还是枝干节点都有着相同的结构,那意味着我们无法通过geChildren方法得到子节点的类型,而必须在方法实现的内部进行判断,代码如下:
package com.guifa.patterndemo.compositepattern;
public abstract class Component {
// 节点名
protected String name;
public Component(String name) {
this.name = name;
}
// 具体的逻辑方法由子类实现
public abstract void doSomething();
/**
* 添加子节点
*
* @param child 子节点
*/
public abstract void addChild(Component child);
/**
* 移除子节点
*
* @param child 子节点
*/
public abstract void removeChild(Component child);
/**
* 获取子节点
*
* @param index 子节点对应下标
* @return 子节点
*/
public abstract Component getChildren(int index);
}
package com.guifa.patterndemo.compositepattern;
import java.util.ArrayList;
import java.util.List;
public class Composite extends Component {
/**
* 存储节点的容器
*/
private List<Component> components = new ArrayList<>();
public Composite(String name) {
super(name);
}
@Override
public void doSomething() {
System.out.println(name);
if (null != components) {
for (Component c : components) {
c.doSomething();
}
}
}
/**
* 添加子节点
*
* @param child 子节点
*/
public void addChild(Component child) {
components.add(child);
}
/**
* 移除子节点
*
* @param child 子节点
*/
public void removeChild(Component child) {
components.remove(child);
}
/**
* 获取子节点
*
* @param index 子节点
*/
public Component getChildren(int index) {
return components.get(index);
}
}
package com.guifa.patterndemo.compositepattern;
public class Leaf extends Component {
public Leaf(String name) {
super(name);
}
@Override
public void doSomething() {
System.out.println(name);
}
@Override
public void addChild(Component child) {
throw new UnsupportedOperationException("叶子节点没有子节点");
}
@Override
public void removeChild(Component child) {
throw new UnsupportedOperationException("叶子节点没有子节点");
}
@Override
public Component getChildren(int index) {
throw new UnsupportedOperationException("叶子节点没有子节点");
}
}
package com.guifa.patterndemo.compositepattern;
public class Client {
public static void main(String[] args) {
// 构造一个根节点
Composite root = new Composite("Root");
// 构造两个枝干节点
Composite branch1 = new Composite("Branch1");
Composite branch2 = new Composite("Branch2");
// 构造两个叶子节点
Leaf leaf1 = new Leaf("Leaf1");
Leaf leaf2 = new Leaf("Leaf2");
// 将叶子节点添加至枝干节点中
branch1.addChild(leaf1);
branch2.addChild(leaf2);
// 将枝干节点添加至根节点中
root.addChild(branch1);
root.addChild(branch2);
// 执行方法
root.doSomething();
}
}
组合模式的简单实现
我们以电脑上的文件系统为例,看看一个简单的文件系统是如何构成的,首先声明一个Dir抽象类表示文件和文件夹。
package com.guifa.patterndemo.compositepattern;
import java.util.ArrayList;
import java.util.List;
public abstract class Dir {
protected List<Dir> dirs = new ArrayList<>();
// 当前文件或文件夹名
private String name;
public Dir(String name) {
this.name = name;
}
/**
* 添加一个文件或文件夹
*
* @param dir 文件或文件夹
*/
public abstract void addDir(Dir dir);
/**
* 移除一个文件或文件夹
*
* @param dir 文件或文件夹
*/
public abstract void rmDir(Dir dir);
/**
* 清空文件夹下所有元素
*/
public abstract void clear();
/**
* 输出文件夹目录结构
*/
public abstract void print();
/**
* 获取文件夹下所有的文件或文件夹
*
* @return 文件夹下所有的文件或子文件夹
*/
public abstract List<Dir> getFiles();
/**
* 获取文件或文件夹名
*
* @return 文件或文件夹名
*/
public String getName() {
return name;
}
}
在该抽象类中我没定义了相关的抽象方法,大家可以看到这里用到的就是所谓的透明的组合模式。
package com.guifa.patterndemo.compositepattern;
import java.util.Iterator;
import java.util.List;
public class Folder extends Dir {
public Folder(String name) {
super(name);
}
@Override
public void addDir(Dir dir) {
dirs.add(dir);
}
@Override
public void rmDir(Dir dir) {
dirs.remove(dir);
}
@Override
public void clear() {
dirs.clear();
}
@Override
public void print() {
System.out.print(getName() + "(");
Iterator<Dir> iterator = dirs.iterator();
while (iterator.hasNext()) {
Dir dir = iterator.next();
dir.print();
if (iterator.hasNext()) {
System.out.print(",");
}
}
System.out.print(")");
}
@Override
public List<Dir> getFiles() {
return dirs;
}
}
package com.guifa.patterndemo.compositepattern;
import java.util.List;
public class File extends Dir {
public File(String name) {
super(name);
}
@Override
public void addDir(Dir dir) {
throw new UnsupportedOperationException("文件对象不支持该操作");
}
@Override
public void rmDir(Dir dir) {
throw new UnsupportedOperationException("文件对象不支持该操作");
}
@Override
public void clear() {
throw new UnsupportedOperationException("文件对象不支持该操作");
}
@Override
public void print() {
System.out.print(getName());
}
@Override
public List<Dir> getFiles() {
throw new UnsupportedOperationException("文件对象不支持该操作");
}
}
package com.guifa.patterndemo.compositepattern;
public class Client {
public static void main(String[] args) {
// 构造一个目录对象表示C盘根目录
Dir diskC = new Folder("C");
// C盘根目录下有一个文件ImbaMallLog.txt
diskC.addDir(new File("ImbaMallLog.txt"));
// C盘根目录下还有3个子目录Windows、PerfLogs、Program File
Dir dirWin = new Folder("Windows");
// Windows目录下有文件explorer.exe
dirWin.addDir(new File("explorer.exe"));
diskC.addDir(dirWin);
// PerfLogs目录
Dir dirPer = new Folder("PerfLogs");
// PerfLogs目录下有文件null.txt
dirPer.addDir(new File("null.txt"));
diskC.addDir(dirPer);
// Program File目录
Dir dirPro = new Folder("Program File");
// Program File目录下有文件ftp.txt
dirPro.addDir(new File("ftp.txt"));
diskC.addDir(dirPro);
// 打印出文件结构
diskC.print();
}
}
输出结果:
C(ImbaMallLog.txt,Windows(explorer.exe),PerfLogs(null.txt),Program File(ftp.txt))
Process finished with exit code 0
这里我们以括号作为一个文件夹的内容范围,如上输出所示,C盘文件夹下有3个子文件夹Windows、PerfLogs和Program File以及一个文件ImbaMallLog.txt,而且3个子文件夹中还各自包含有子文件,一个典型的树状嵌套结构,这就是组合模式。
总结
组合模式的优点:
组合模式可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,它让高层模块忽略了层次的差异,方便对整个层次结构进行控制。
高层模块可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构,简化了高层模块的代码。
在组合模式中增加新的枝干构件和叶子构件都很方便,无须对现有类库进行任何修改,符合“开闭原则”。
组合模式为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子对象和枝干对象的递归组合,可以形成复杂的树形结构,但对树形结构的控制却非常简单。
组合模式的缺点:
在新增构件时不好对枝干中的构件类型进行限制,不能依赖类型系统来施加这些约束,因为在大多数情况下,它们都来自于相同的抽象层,此时,必须进行类型检查来实现,这个实现过程较为复杂。