类别:结构型设计模式
目的:表示一组对象,这组对象的用法和单个对象的用法一致
完整代码参考:https://1drv.ms/u/s!AquRvPzqx59Ri3TfmAb69-DF82jQ?e=DR2vwn
典型场景
处理电脑上的文件和文件夹
基本事实
- 文件和文件夹可以有多个
- 文件和文件夹有多个共同的操作,比如:删除、移动、复制等
这里拿对多个文件夹和文件执行删除操作举例,对应mac下右键Move to Trash按钮
可以看到,可以选中多个文件和文件夹执行删除、复制、移动、获取属性信息等操作
硬编码
这里拿在业务逻辑中删除2个文件和1个文件夹举例
文件File和文件夹Folder类原型
public class File {
private String path;
public File(String path) {
this.path = path;
}
public void delete() {
System.out.println("file: " + path + " was deleted.\n");
}
}
public class Folder {
private String path;
public Folder(String path) {
this.path = path;
}
public void delete() {
System.out.println("folder: " + path + " was deleted.\n");
}
}
为了能够同时删除多个文件/文件夹、很容易写出下面的聚合代码
聚合删除文件和文件夹Group类
public class Group {
private List<Object> items = new ArrayList<>();
public void add(Object object) {
items.add(object);
}
public void delete() {
for (Object object : items) {
if (object instanceof File) {
((File) object).delete();
} else if (object instanceof Folder) {
((Folder) object).delete();
}
}
}
}
实际使用举例如下
public class Main {
public static void main(String[] args) {
var file1 = new File("1.txt");
var file2 = new File("2.txt");
var folder1 = new Folder("/tmp/folder1");
var group = new Group();
group.add(file1);
group.add(file2);
group.add(folder1);
businessLogic(group);
}
public static void businessLogic(Group group) {
group.delete();
}
}
核心问题
文件和文件夹的删除逻辑和类型耦合了,参考Group::delete()方法、随着类型的细分,比如文件类型继续细分为压缩文件和图片文件,那么delete()中对类型的耦合会越来越多
如果还存在其它批量操作,比如批量获取文件/文件夹信息,比如Group::getInfo()方法,name这种耦合代码还将重复一次
解决方式:组合模式,利用组合模式重构代码结构,使得文件和文件夹、分组都实现一个公有操作的接口,比如
模式实现
public interface Component {
void delete();
}
结构更改如下
public class File implements Component
public class Folder implements Component
public class Group implements Component
然后在Group::delete()时就无须关心类型了,所有的Component类型都有delete方法了,实现变更为:
public void delete() {
for (Component component : items) {
component.delete();
}
}
可以参考完整代码
执行效果如下:
可以看到文件和文件夹的删除操作都被调用到了,分组Group也可以像文件和文件夹一样被add进别的分组了,达到了组合模式,一个组合可以被当成独立个体使用的目的,参考下面的group2:
public static void main(String[] args) {
var file1 = new File("1.txt");
var file2 = new File("2.txt");
var folder1 = new Folder("/tmp/folder1");
var group = new Group();
group.add(file1);
group.add(file2);
group.add(folder1);
var group2 = new Group();
group2.add(group);
group2.add(new File("3.txt"));
businessLogic(group2);
}
public static void businessLogic(Group group) {
group.delete();
}
UML
一些注意的点
需要使用接口技术使得不同类型的对象保持相同的行为
为什么组合模式更好
- 避免大量的类型检查逻辑
- 避免重复的类型检查逻辑