设计模式之组合模式详解(Java实现)

1 组合模式介绍

在我们的树形目录结构中,包含文件和文件夹两类不同的元素,如下图。

image-20220425095629572

其中文件夹中可以包含文件,也可以继续包含文件夹;而在文件中不能再包含子文件或者子文件夹。

那么我们可以将文件夹看作是容器(Container),将文件看作是叶子(Leaf)。那如何一致的对待容器对象和叶子对象呢?

组合模式(Composite Pattern)让客户端可以统一对待单个对象和组合对象。这种模式创建了一个包含自己对象组的类。该类提供了修改相同对象组的方式。

主要解决: 它在我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以像处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。

何时使用: 1、您想表示对象的部分-整体层次结构(树形结构)。 2、您希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。

如何解决: 树枝和叶子实现统一接口,树枝内部组合该接口。

关键代码: 树枝内部组合该接口,并且含有内部属性 List,里面放 Component。

应用实例: 1、算术表达式包括操作数、操作符和另一个操作数,其中,另一个操作数也可以是操作数、操作符和另一个操作数。 2、在 JAVA AWT 和 SWING 中,对于 Button 和 Checkbox 是树叶,Container 是树枝。

优点: 1、高层模块调用简单。 2、节点自由增加。

缺点: 在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了依赖倒置原则。

使用场景: 部分、整体场景,如树形菜单,文件、文件夹的管理。

注意事项: 定义时为具体类。

2 组合模式详解

2.1 组合模式结构

组合模式的结构如图所示:

image-20220425180628947

由图可知,组合模式包含以下3个角色。

  1. Component(抽象构件):它可以是接口或抽象类,为叶子构件和容器构件对象声明接口,在该角色中可以包含所有子类共有行为的声明和实现。在抽象构件中定义了访问及管理它的子构件的方法,如增加子构件、删除子构件、获取子构件等。
  2. Leaf(叶子构件):它在组合结构中表示叶子结点对象,叶子结点没有子结点,它实现了在抽象构件中定义的行为。对于那些访问及管理子构件的方法,可以通过抛出异常、提示错误等方式进行处理。
  3. Composite(容器构件):它在组合结构中表示容器结点对象,容器结点包含子结点,其子结点可以是叶子结点,也可以是容器结点,它提供一个集合用于存储子结点,实现类在抽象构件中定义的行为,包括那些访问及管理子构件的方法,在其业务方法中可以递归调用其子结点的业务方法。

2.2 组合模式实现

对于组合模式中的抽象构件角色,其典型代码如下:

1
2
3
4
5
6
public abstract class Component {
    public abstract void addComponent(Component c); // 增加成员
    public abstract void remove(Component c); // 删除成员
    public abstract Component getChild(int i); // 获取成员
    public abstract void operation(); // 业务方法
}

对于组合模式中的叶子构件角色,其典型代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class Leaf extends Component {
    public void add(Component c) {
        // 异常处理或错误提示
    }
    public void remove(Component c) {
        // 异常处理或错误提示
    }
    public void getChild(int i) {
        // 异常处理或错误提示
    }
    public void operation() {
        // 叶子构件具体业务方法的实现
    }
}

对于组合模式中的容器构件角色,其典型代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import java.util.*;

public class Composite extends Component {
    private ArrayList<Component> list = new ArrayList<Component>();
    
    public void add(Component c) {
        list.add(c);
    }
    public void remove(Component c) {
        list.remove(c);
    }
    public void getChild(int i) {
        return (Componnent) list.get(i);
    }
    public void operation() {
        // 容器构件具体业务方法的实现,将递归调用成员构件的业务方法
        for (Object object : list) {
            ((Component)obj).operation();
        }
    }
}

2.3 组合模式应用举例

  • 题目描述

    某软件公司欲开发一个杀毒(Antivirus)软件,该软件既可以对某个文件夹(Folder)杀毒,也可以对某个指定的文件(File)进行杀毒。该杀毒软件还可以根据各类文件的特点,为不同类型的文件提供不同的杀毒方式,例如图像文件(ImageFile)和文本文件(TextFile)的杀毒方式就有所差异。现使用组合模式来设计该杀毒软件的整体框架。

  • UML类图 image-20220425192448502

    其中,AbstractFile充当抽象构件类,Folder充当容器构件类,ImageFile、TextFile和VideoFile充当叶子构件类。

  • 代码

    代码地址

3 透明组合模式和安全组合模式

组合模式根据抽象构件的定义形式又可以分为透明组合模式和安全组合模式。

  • 透明组合模式

    image-20220425195327157

    根据结构图我们可以看出抽象构件Component中声明了所有用于管理成员对象的方法,包括add()、remove(),以及getChild()等方法,在客户端看来,叶子对象与容器对象所提供的方法是一致的,客户端可以一致地对待所有的对象,缺点是不够安全,因为叶子对象和容器对象在本质上是有区别的。

  • 安全组合模式

    image-20220425195519443

    根据结构图可以看出抽象构件Component中没有声明任何用于管理成员对象的方法,而是在Composite类中声明并实现这些方法,对于叶子对象,客户端不可能调用到这些方法。缺点是不够透明,客户端不能完全针对抽象编程,必须有区别地对待叶子构件和容器构件。但在实际应用中,安全组合模式的使用频率也非常高。


相关内容

Buy me a coffee~
HeZephyr 支付宝支付宝
HeZephyr 微信微信
0%