本文主要介绍:组合模式原理和用法,以及组合模式的透明和安全实现。
模式背景
在书中给了一个案例:我们对给定的文件夹进行杀毒。文件夹和和子文件夹都需要递归进行查杀,并且需要依据文件是图片还是文本需要采用不同的查杀方式。
这是一个比较常见的业务场景。相当于我们要递归遍历一个树形结构,但是这个树里面的节点类型有好几种。我们自然会为每种类型的节点创建对应的类,但是问题出现在包含子文件夹和文件的文件夹这个类上(也就是非叶子节点)。对于这种节点,我们就需要对自己下面的文件类型进行区分,然后采用不同的杀毒策略。
这样就出现了一个问题:1. 非叶子节点的处理逻辑就很复杂,我们每种文件类型需要做区分在去调用查杀。2. 一旦添加了一个新的文件种类,需要修改文件夹类代码。
所以我们希望能让系统进行杀毒操作的时候,无论对什么文件和文件夹,他们都用同一套对外接口来操作,文件夹的代码就可以简化操作这个同一套标准的接口。
定义&概念
组合模式是对单个对象和组合对象(叶子节点和非叶子节点)抽象,使其使用具有一致性,组合模式又叫“整体-部分”模式。是一种对象结构型模式。对于树形的对象结构群体来说很有用。组合模式是使用面向对象的思想来实现树形结构的构建和处理。
原理
前面我们说过,问题主要出在了文件夹类进行处理的时候,各个文件不同需要做区分并调用查杀方法。现在希望能够有统一的调用接口。这就很简单:抽象!我们将文本文件,图片文件,还是文件夹都抽象公共部分出来。然后操作都是通过这个抽象来进行操作。以后添加什么新的文件类型,也只需要添加一个抽象的新的实现就行了。
所以可以理解为:组合模式主要的核心思想,是将树型结构中的节点抽象出一个抽象构建类。
必要条件
- Component(抽象的节点类)
- 这是组合模式的关键,就是将树形结构中的叶子节点和非叶子节点都抽象出一个父级(可以是接口也可以是抽象类)。这个抽象里面包含叶子和非叶子所有的属性和方法。可以广义的代表该树形体系中所有的节点。(是否组合模式就看是否将结构抽象出这样的一个公共抽象)
- Leaf(叶子节点)
- 就是树形结构中不包含子节点的那些Component的实现。
- Composite(容器节点)
- 非叶子节点部分,里面有个List,存放自己的子节点们。
因为系统中节点的种类不同,一般都可以会有各自的方法,比如文件夹节点有add,remove方法,但是叶子节点就没有。这时候我们对Component抽象的层次可以做一些控制,我们按照抽象层次程度分为透明模式和安全模式:
- 透明模式
- 抽象构建类定义了所有的方法。就是我们一直在说的组合模式。
- 说他不够安全因为,如果在运行中调用了叶子节点的add,remove方法(这些方法本身对于叶子没有意义)可能会出错。
- 安全模式
- 抽象构建类中只定义通用的方法。叶子还是中间节点他们各自需要的方法,他们自己实现。
当然我们还可以在抽象构建类中对方法提供默认实现,需要使用该方法的进行覆写即可,这也是不透明的。都只是为了减少编写无用的代码,但是提供不了强约束。
UML
透明组合模式
安全组合模式
实现
透明组合模式
抽象层拥有所有的方法,这些方法可能叶子节点是不应该有的。 比如add和remove子节点。
抽象
public interface Component {
void add(Component c);
void remove(Component c);
List<Component> getChild();
void operation();
}
叶子
class Leaf implements Component {
private String name;
public Leaf(String name) {
this.name = name;
}
@Override
public void add(Component c) {
System.out.println("can't add");
}
@Override
public void remove(Component c) {
System.out.println("can't remove");
}
@Override
public List<Component> getChild() {
return null;
}
@Override
public void operation() {
System.out.println("叶子节点:" + name);
}
}
非叶子
class Composite implements Component {
private List<Component> componentList = new ArrayList<>();
private String name;
public Composite(String name) {
this.name = name;
}
@Override
public void add(Component c) {
componentList.add(c);
}
@Override
public void remove(Component c) {
componentList.remove(c);
}
@Override
public List<Component> getChild() {
return componentList;
}
@Override
public void operation() {
System.out.println("非叶子节点:" + name);
componentList.forEach(Component::operation);
}
}
使用
Component c1, c2, c3, f1, f2, f3;
c1 = new Leaf("1");
c2 = new Leaf("2");
c3 = new Leaf("3");
f1 = new Composite("1");
f2 = new Composite("2");
f3 = new Composite("3");
f1.add(c1);
f1.add(c2);
f2.add(f1);
f3.add(c3);
f1.add(f3);
f1.operation();
优点
- 简单,保证了叶子节点和非叶子节点具有对外一致性,在对该树形节点递归处理的时候可以同等对待。
缺点
- 不安全,叶子的特性(不能添加子节点)有可能被破坏,一旦错误调用可能会出问题,没有代码约束保证。
- 其次,每次叶子节点实现抽象的时候,我们需要实现全部方法,add和remove这些就本是没必要去实现的,这就增加了编码复杂度。
安全组合模式
安全组合模式就是让抽象层剥离叶子节点不需要的方法,具体的实现让Composite自己实现。
实现1
我们可以使用适配器模式的缺省适配器来适配一个抽象,实现叶子的不需要的默认方法。然后让叶子节点来实现这个类。
public abstract class AbstractComponent implements Component {
@Override
public void add(Component c) {
System.out.println("no support");
}
@Override
public void remove(Component c) {
System.out.println("no support");
}
@Override
public List<Component> getChild() {
System.out.println("no support");
return null;
}
@Override
public abstract void operation();
}
实现2
抽象构建中只定义公共方法,其他的他们自己用自己定义。
public interface ComponentSafe {
void operation();
}
叶子
class LeafSafe implements ComponentSafe {
private String name;
public LeafSafe(String name) {
this.name = name;
}
@Override
public void operation() {
System.out.println("叶子:" + name);
}
}
非叶子
class CompositeSafe implements ComponentSafe {
private String name;
private List<ComponentSafe> componentSafeList = new ArrayList<>();
public CompositeSafe(String name) {
this.name = name;
}
@Override
public void operation() {
System.out.println("非叶子节点" + name);
componentSafeList.forEach(ComponentSafe::operation);
}
public void add(ComponentSafe c) {
componentSafeList.add(c);
}
public void remove(ComponentSafe c) {
componentSafeList.remove(c);
}
public List<ComponentSafe> getChild() {
return componentSafeList;
}
}
使用
ComponentSafe c1, c2, c3;
c1 = new LeafSafe("1");
c2 = new LeafSafe("2");
c3 = new LeafSafe("3");
CompositeSafe f1, f2, f3;
f1 = new CompositeSafe("1");
f2 = new CompositeSafe("2");
f3 = new CompositeSafe("3");
//build
f1.add(c1);
f1.add(c2);
f1.add(c3);
f2.add(f3);
f1.add(f2);
f1.operation();
优点
- 安全了,叶子节点不用管不属于他的方法了,有强约束里保证,叶子节点决定不会调用到add remove方法。
缺点
- 但是不透明了,构建叶子和构建容器需要使用不同的方法来构建。我们无法只面对抽象进行编程。
使用场景
适用于有整体和部分的层次结构,这里的整体和部分指的是系统中类,他们的大致功能是相同的,小部分存在差异。我们如果希望以一种方式忽略整体和部分的差异,让客户单可以直接面向抽象编程,使用统一的标准对待他们。
有树形结构的使用场景,如树形菜单,文件、文件夹的管理。
总结
总的来说,组合模式的核心就是对系统中大同小异的类做抽象!以方便于客户端直接面向抽象编程。透明还是安全的结合场景选择最适合的。
附
如有代码和文章问题,还请指正!感谢!