Composite Pattern 组合模式,作为一种结构型设计模式,其通常适用于具有树形结构的场景
基本原理
现实世界中树形结构非常常见,比如公司的部门-人员的组织架构、磁盘目录下的文件夹-文件结构、XML文件等。在树形结构下其通常有两种类型节点:叶子节点、树枝节点。以磁盘目录为例,叶子节点即文件,树枝节点即文件夹。在软件开发中,如果对这两种类型节点分别处理,将会变得十分麻烦。那么有没有办法可以使得对这两种类型节点的处理可以统一呢?答案就是组合模式,通过组合多个对象形成树形结构来表示”整体—部分”关系的层次结构,其对叶子节点和树枝节点的使用具有一致性
组合模式的角色比较简单,其只有三种角色:
- 抽象组件角色:为了保证客户端对两种类型节点的使用的一致性,我们需要定义一个二者公共的接口或抽象类,其声明了二者共有的方法、属性。即下文示例的FileSystem类
- 叶子组件角色:其是对抽象组件角色的具体实现。显然对于一个叶子节点而言,其下是没有子节点的。即下文示例的File类
- 树枝组件角色:其亦是对抽象组件角色的实现,其表示的是树枝类型节点。其内部会有一个容器用于存放子节点,同时支持添加、删除等一系列对子节点的操作。即下文示例的Folder类
实现
透明组合模式
现在,让我们通过Java来实现文件系统的文件夹-文件的组织结构,来帮助大家更好的理解组合模式。首先,定义一个公共接口——FileSystem类。其中定义了文件(即叶子节点)、文件夹(即树枝节点)中所需的属性和方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
|
public abstract class FileSystem {
private String name;
public FileSystem(String name) { this.name = name; }
public void getInfo() { System.out.println("[NAME]: " + name); }
abstract public void add(FileSystem fileSystem);
abstract public void remove(FileSystem fileSystem);
abstract public List<FileSystem> getChildren(); }
|
然后对于叶子类型节点的File类而言,对于不支持的方法(因这些方法属于文件夹的操作),可选择抛出异常或者空实现,这里我们选择前者,以便当client对文件对象调用不支持的操作时进行提醒
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
public class File extends FileSystem { public File(String name) { super(name); }
@Override public void add(FileSystem fileSystem) { throw new UnsupportedOperationException("不支持的操作"); }
@Override public void remove(FileSystem fileSystem) { throw new UnsupportedOperationException("不支持的操作"); }
@Override public List<FileSystem> getChildren() { throw new UnsupportedOperationException("不支持的操作"); } }
|
而对于树枝组件Folder文件夹类,我们则需要在内部持有容器来存放子节点,并实现文件夹操作子节点的相关方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
|
public class Folder extends FileSystem {
private List<FileSystem> list = new LinkedList<>();
public Folder(String name) { super(name); }
@Override public void add(FileSystem fileSystem) { list.add(fileSystem); }
@Override public void remove(FileSystem fileSystem) { list.remove(fileSystem); }
@Override public List<FileSystem> getChildren() { return list; } }
|
至此磁盘目录的文件夹-文件树状结构就已经实现完成了,现在我们来看看client的使用姿势。同时也一并提供了遍历某个目录下的所有节点的showTree方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
|
public class TransparencyCompositePatternDemo { public static void main(String[] args) { Folder root = new Folder("863重点项目");
File file1 = new File("README.md"); File file2 = new File("LICENSE.txt"); Folder folderC = new Folder("C代码"); Folder folderJava = new Folder("Java代码"); root.add(file1); root.add(file2); root.add(folderC); root.add(folderJava);
File fileC1 = new File("main.c"); File fileC2 = new File("test.c"); folderC.add(fileC1); folderC.add(fileC2);
File fileJava1 = new File("webSocketServer.java"); File fileJava2 = new File("tcpServer.java"); File fileJava3 = new File("updServer.java"); folderJava.add(fileJava1); folderJava.add(fileJava2); folderJava.add(fileJava3);
System.out.println("基于透明模式的组合模式"); showTree(root); }
public static void showTree(FileSystem root) { for( FileSystem fileSystem : root.getChildren() ) { fileSystem.getInfo(); if( fileSystem instanceof Folder) { showTree(fileSystem); } } } }
|
测试结果如下,符合测试预期。在本实现方案中,我们将仅适用于文件夹类的操作方法也一并放在抽象类进行声明,故该实现通常被称为透明组合模式。对于Client而言,操作File、Folder对象时必须保证节点类型判断无误、处理得当,否则会导致运行期发生意外,即所谓的不安全
安全组合模式
透明组合模式最大的问题在于不够安全,而解决该问题也很简单。我们将抽象组件角色FileSystem类中关于文件夹(即树枝节点)的方法声明移除掉,而仅仅在树枝组件Folder文件夹类中定义它们。这样一旦client期望通过叶子节点File类非法调用它们,即会导致编译期错误,便于及时发现、排查、修复。这就是所谓的安全组合模式。现在,我们的抽象组件角色FileSystem类中就仅仅包含叶子、树枝节点共有的方法了
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
public abstract class FileSystem { private String name;
public FileSystem(String name) { this.name = name; }
public void getInfo() { System.out.println("[NAME]: " + name); } }
|
而叶子节点File类自然也精简很多,没有那些冗余的方法定义了
1 2 3 4 5 6 7 8
|
public class File extends FileSystem { public File(String name) { super(name); } }
|
最后只在树枝组件Folder文件夹类中定义其特有的相关方法即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
public class Folder extends FileSystem { private List<FileSystem> list = new LinkedList<>();
public Folder(String name) { super(name); }
public void add(FileSystem fileSystem) { list.add(fileSystem); }
public void remove(FileSystem fileSystem) { list.remove(fileSystem); }
public List<FileSystem> getChildren() { return list; } }
|
至此,一个安全组合模式就已经实现完毕了。下面让我们通过如下测试用例来看看效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| public class SafetyCompositePatternDemo { public static void main(String[] args) { Folder root = new Folder("863重点项目");
File file1 = new File("README.md"); File file2 = new File("LICENSE.txt"); Folder folderC = new Folder("C代码"); Folder folderJava = new Folder("Java代码"); root.add(file1); root.add(file2); root.add(folderC); root.add(folderJava);
File fileC1 = new File("main.c"); File fileC2 = new File("test.c"); folderC.add(fileC1); folderC.add(fileC2);
File fileJava1 = new File("webSocketServer.java"); File fileJava2 = new File("tcpServer.java"); File fileJava3 = new File("updServer.java"); folderJava.add(fileJava1); folderJava.add(fileJava2); folderJava.add(fileJava3);
System.out.println("基于安全模式的组合模式"); showTree(root); }
public static void showTree(Folder root) { for( FileSystem fileSystem : root.getChildren() ) { fileSystem.getInfo(); if( fileSystem instanceof Folder) { showTree( (Folder) fileSystem); } } } }
|
测试结果如下,符合预期
参考文献
- Head First 设计模式 弗里曼著