Java设计模式之组合模式(Composite Pattern)

介绍 组合模式是一种结构型设计模式。它一般是 用来创建树状的结构,表示“部分-整体”的层次关系。由于该模式使用的是对象组合的方式来实现的,区别于继承的方式,因此也叫做合成模式。先来看一下它的定义:

Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly.

将对象组合成树形结构以表示 “部分-整体” 的层次结构,使得用户对单个对象和组合对象的使用具有一致性。
组合模式主要包括三种角色:
  • Componet抽象构件角色。定义对象的框架,可以在其中实现通用的方法、定义类的属性等等,单个对象和组合对象都要继承它。
  • Leaf叶子组件。相当于树状结构中的叶子,是结构的最下层,下面没有其他组件。
  • Composite树枝组件。相当于树状结构的分枝节点,下面有其他树枝组件或叶子组件,它相当于一个容器,包含下面的各个组件,是其下各个组件的父亲。
有了这三种角色就可以实现组合模式。组合模式的应用有很多,最常见的是我们的文件系统,一个文件夹下面可以有很多其他的文件夹,并且还可以有很多文件,这里的文件夹就相当于我们的树枝组件(容器),而文件就相当于叶子组件(不会包含其他组件)。
组合模式有两种不同的实现:安全模式和透明模式。从名字就可以看出来,安全模式比透明模式更安全。其实安全模式是通过 将树枝和树叶组件分开定义 来实现其安全性的,而透明模式是不区分的。下面来具体介绍组合模式。
注:在透明模式的实现中,树枝的方法放到了抽象构件中,因此透明模式实现的组合模式只有两种角色。
安全模式 拿我们的文件系统举例,这里需要二者的抽象构件:
public abstract class FileSystem { protected String fileName = ""; public FileSystem(String fileName) { this.fileName = fileName; } }

然后定义我们的树枝和树叶组件,也就是我们的文件夹和文件:
文件夹:
public class Folder extends FileSystem { private List fileList = new ArrayList<>(); public Folder(String fileName) { super(fileName); } //新建文件夹或文件。 public void add(FileSystem fileSystem) { this.fileList.add(fileSystem); } //删除文件夹或文件。 public void remove(FileSystem fileSystem) { this.fileList.remove(fileSystem); } //获得下面的所有文件、文件夹信息。 public List getChildren() { return this.fileList; } @Override public String toString() { String name = "文件夹名:" + this.fileName + "\n"; for (FileSystem fileSystem : fileList) { name = name + fileSystem; } return name; } }

文件:
public class File extends FileSystem { public File(String fileName) { super(fileName); } @Override public String toString() { return "文件名:" + this.fileName + "\n"; } }

最后,三种组件都定义好后,就可以开始我们的测试了:
public static void main(String[] args) { Folder root = new Folder("我的电脑"); Folder branch = new Folder("我的图片"); File leaf = new File("图片.jpg"); root.add(branch); branch.add(leaf); display(root); } public static void display(Folder root) { for (FileSystem fileSystem : root.getChildren()) { System.out.println(fileSystem); } }

输出结果:
文件夹名:我的图片
文件名:图片.jpg
这就是安全模式实现的组合模式,它通过将树枝和树叶分开定义来保证系统的安全性。
透明模式 透明模式是将安全模式中具体类中的方法放到了抽象类中,例如 add() 方法、 remove()方法和 getChildren() 方法等。来看我们更改后的代码:
抽象构件:
public abstract class FileSystem { protected List fileList = new ArrayList<>(); protected String fileName = ""; public FileSystem(String fileName) { this.fileName = fileName; } //新建文件夹或文件。 public void add(FileSystem fileSystem) { this.fileList.add(fileSystem); } //删除文件夹或文件。 public void remove(FileSystem fileSystem) { this.fileList.remove(fileSystem); } //获得下面的所有文件、文件夹信息。 public List getChildren() { return this.fileList; } @Override public String toString() { String name = "文件夹名:" + this.fileName + "\n"; for (FileSystem fileSystem : fileList) { name = name + fileSystem; } return name; } }

文件夹和文件是相同的:
public class File extends FileSystem { public File(String fileName) { super(fileName); } //透明模式。 @Override public String toString() { String name = "文件夹名:" + this.fileName + "\n"; for (FileSystem fileSystem : this.fileList) { name = name + fileSystem; } return name; } }

最后的测试代码,与安全模式稍有不同:
public static void main(String[] args) { File root = new File("我的电脑"); File branch = new File("我的图片"); File leaf = new File("图片.jpg"); root.add(branch); branch.add(leaf); display(root); } public static void display(FileSystem root) { for (FileSystem fileSystem : root.getChildren()) { System.out.println(fileSystem); } }

输出结果:
文件夹名:我的图片
文件夹名:图片.jpg
可以看到,该模式中的树枝组件和树叶组件结构是一样的,这样就比安全模式少了一个实现类。但这样就会出现一个问题,由于叶子组件是不能使用这些方法的,但是方法放在抽象类中的话,它就可以使用了。这就会造成潜在的危险。虽然叶子组件可以在这些方法可以重写,但并没有什么必要,因此这里还是建议使用安全模式来实现组合模式。
总结 组合模式在创建数据库系统等复杂的对象时,是非常合适的,尤其是树形结构的对象。它的优点有两点:
  • 无论树枝组件还是树叶组件,对调用者来讲是没有任何区别的,高层模块不必关心处理的是组合对象还是单个对象。
  • 可以自由增加对象。就像文件系统一样,增加或删除一个文件夹或文件,仅仅只对当前的文件夹有影响,其他部分还是正常工作,这样的开发非常符合开闭原则,扩展十分容易。
【Java设计模式之组合模式(Composite Pattern)】然而组合模式也有其缺点,最大的缺点就是使用时直接使用其实现类,这与依赖倒置原则相冲突。

    推荐阅读