前言
组合模式是属于结构型设计模式中的一种,它适用于树状结构类型的对象使用。用来表示“整体-部分”的关系,使用户对单个对象和组合对象具有一致的访问性。
怎么理解这句话呢?举个例子:一般电脑文件夹下面会包含不同的文件以及子文件夹,子文件夹里又包含文件和子文件夹……
图示(来源网络):
这种文件系统就是一个典型的树形结构。组合模式则是专门适用于这种树形结构对象的设计。整个文件系统可以看作一个整体,单个子文件或者文件夹可以看作部分。不止是文件系统,所有可以转成树形结构的对象都可以使用组合模式。比如:公司、部门、员工。
一、组合模式
组合模式分为透明式的组合模式和安全式的组合模式。先看下组合模式的结构角色:
- 抽象构件(Component)角色:它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。在透明式的组合模式中抽象构件还声明访问和管理子类的接口;在安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝构件完成。(总的抽象类或接口,定义一些通用的方法,比如新增、删除)
- 树叶构件(Leaf)角色:是组合中的叶节点对象,它没有子节点,用于继承或实现抽象构件。
- 树枝构件(Composite)角色 / 中间构件:是组合中的分支节点对象,它有子节点,用于继承和实现抽象构件。它的主要作用是存储和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法。
组合模式的结构比较简单,主要是由一个抽象类/接口和几个不同的实现子类组成。组合模式可分为透明式组合以及安全式组合,二者的区分在于:抽象构件角色里是否定义了所有子类会用到的方法。
透明组合模式:抽象类/接口中定义所有子类会用到的方法(不只+是公共方法,是所有子类会用到的方法),子类可以直接重写,不用去额外去定义父类之外的方法。而后客户端/调用者就可以完全针对抽象类/接口编程。内部的实现对于调用者来说就完全透明。
图示(来源网络):
上面图示的Component为公共的抽象类,他里面不仅定义了公共{operation}方法,还定义了树叶节点用不上的{add、remove、getChild}三个方法。这三个方法是树枝节点需要的。所以透明组合下,树叶节点就得对这个三个方法提供空实现或者给个报错提示。
安全组合模式:抽象类/接口中只定义公共方法,具体每个不同子类会需要不同的方法就由子类自己定义。安全组合模式下,调用者就不能完全的针对抽象接口编程了,有一些子类就需要暴露给调用方。
图示(来源网络):
上面图示中Component抽象接口只定义了公共方法,具体树枝构建所需的{add、remove、getChild}方法则由树枝角色自己定义。
二、组合模式使用
接下来以上面的文件系统为例,使用组合模式完成对文件系统的杀毒。首先拆分设计,将文件系统拆分成文件夹、文件两种类型。文件夹下有子文件夹和文件。以组合模式代入。文件夹对应树枝角色、文件对应树叶角色。
透明组合代码演示
定义抽象构件角色
package com.example.study.composite;
public abstract class Component {
//杀毒公共方法
abstract void killBadProcess();
//文件夹子类里会用到的方法
abstract void add(Component component);
//文件夹子类里会用到的方法
abstract void remove(Component component);
//文件夹子类里会用到的方法
abstract Component getChild(Integer index);
}
文件杀毒实现类:
package com.example.study.composite;
public class FileImpl extends Component{
private String name;
public FileImpl(String name) {
this.name = name;
}
@Override
void killBadProcess() {
System.out.println("对名称为:"+name+"的文件进行杀毒");
}
@Override
void add(Component component) {
//空实现或者报错
}
@Override
void remove(Component component) {
//空实现或者报错
}
@Override
Component getChild(Integer index) {
//空实现或者报错
return null;
}
}
文件夹杀毒实现类:
package com.example.study.composite;
import java.util.ArrayList;
import java.util.List;
public class FolderImpl extends Component{
private String name;
public FolderImpl(String name) {
this.name = name;
}
List<Component> components = new ArrayList<>();
@Override
void killBadProcess() {
System.out.println("-------------------------------------进入"+name+"目录进行杀毒-----------------------");
for (Component component : components) {
component.killBadProcess();
}
}
@Override
void add(Component component) {
components.add(component);
}
@Override
void remove(Component component) {
components.remove(component);
}
@Override
Component getChild(Integer index) {
return components.get(index);
}
}
客户端调用类:
package com.example.study.composite;
public class Client {
public static void main(String[] args) {
Component component = getComponent();
//开始对文件系统杀毒
component.killBadProcess();
}
//构建文件系统
static Component getComponent(){
//定义文件夹
Component folder,folder1,folder2,folder3,folder4,folder5;
//初始根目录
folder = new FolderImpl("根目录");
folder.add(new FileImpl("系统缓存文件"));
folder.add(folder1 = new FolderImpl("文档目录"));
folder.add(folder2 = new FolderImpl("游戏目录"));
//文档目录
folder1.add(new FileImpl("java基础学习文件"));
folder1.add(new FileImpl("设计模式学校文件"));
folder1.add(folder3 = new FolderImpl("数据库目录"));
folder3.add(new FileImpl("数据库学习文件"));
//游戏目录
folder2.add(folder4 = new FolderImpl("lol游戏目录"));
folder4.add(new FileImpl("lol安装文件"));
folder2.add(folder5 = new FolderImpl("cf游戏目录"));
folder5.add(new FileImpl("cf安装文件"));
return folder;
}
}
运行结果:
普通模式下则需要调用类去区分文件类型。然后完成对各个文件类型的杀毒工作。而使用了组合模式后客户端调用类只用了一行代码就完成了对整个文件系统的杀毒工作。把不同的部分组合成一个整体后对外提供。
安全组合代码演示
定义抽象构件角色
package com.example.study.composite;
public abstract class Component {
//只定义杀毒公共方法,其他方法由各自子类定义
abstract void killBadProcess();
}
文件杀毒实现类:
package com.example.study.composite;
public class FileImpl extends Component{
private String name;
public FileImpl(String name) {
this.name = name;
}
@Override
void killBadProcess() {
System.out.println("对名称为:"+name+"的文件进行杀毒");
}
}
文件夹杀毒实现类:
package com.example.study.composite;
import java.util.ArrayList;
import java.util.List;
public class FolderImpl extends Component{
private String name;
public FolderImpl(String name) {
this.name = name;
}
List<Component> components = new ArrayList<>();
@Override
void killBadProcess() {
System.out.println("-------------------------------------进入"+name+"目录进行杀毒-----------------------");
for (Component component : components) {
component.killBadProcess();
}
}
//文件夹子类需要,自己定义
void add(Component component) {
components.add(component);
}
//文件夹子类需要,自己定义
void remove(Component component) {
components.remove(component);
}
//文件夹子类需要,自己定义
Component getChild(Integer index) {
return components.get(index);
}
}
客户端调用类:
package com.example.study.composite;
public class Client {
public static void main(String[] args) {
FolderImpl component = getComponent();
//开始对文件系统杀毒
component.killBadProcess();
}
//构建文件系统
static FolderImpl getComponent(){
//定义文件夹
//安全组合模式下,由于文件夹子类定义了父类没有的其他方法,所以只能声明真实的文件夹类
FolderImpl folder,folder1,folder2,folder3,folder4,folder5;
//初始根目录
folder = new FolderImpl("根目录");
folder.add(new FileImpl("系统缓存文件"));
folder.add(folder1 = new FolderImpl("文档目录"));
folder.add(folder2 = new FolderImpl("游戏目录"));
//文档目录
folder1.add(new FileImpl("java基础学习文件"));
folder1.add(new FileImpl("设计模式学校文件"));
folder1.add(folder3 = new FolderImpl("数据库目录"));
folder3.add(new FileImpl("数据库学习文件"));
//游戏目录
folder2.add(folder4 = new FolderImpl("lol游戏目录"));
folder4.add(new FileImpl("lol安装文件"));
folder2.add(folder5 = new FolderImpl("cf游戏目录"));
folder5.add(new FileImpl("cf安装文件"));
return folder;
}
}
运行结果:
可以对比下透明模式和安全模式下,客户端调用类的区别,透明模式下,客户端可以直接对抽象类进行编程,而安全组合模式下,由于文件夹子类定义了父类没有的其他方法,所以不能直接对抽象类编程,需要使用真实的文件夹类。
总结
组合模式的主要优点有:
- 组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码;
- 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”;
其主要缺点是:
- 设计较复杂,客户端需要花更多时间理清类之间的层次关系;
- 不容易限制容器中的构件;
- 不容易用继承的方法来增加构件的新功能;
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/99031.html