结构模式之组合模式

Posted by Night Field's Blog on April 4, 2020

1 概述

组合模式(Composite Pattern),是指把一类有共同抽象的对象组合在一起,形成树状结构,来表示部分-整体的关系。

2 组合模式

我们经常会遇到一些对象有相同的行为,同时对象之间又有层级结构。像这种情况,使用组合模式可以使系统高内聚,低耦合。通过把类组合成树形结构,调用者能够以统一的方式对待单个对象和整体结构,忽略个体和整体之间的差异。实现组合模式一般需要:

  1. 定义一个接口/抽象类,并定义对象的公共行为,其中包括维护对象间层次结构的方法。
  2. 定义对象的实体,实现接口/抽象类中的方法。
  3. 按树形结构,将所有的实体组合在一起,形成一个整体。

3 案例

看一个例子。所有公司的员工,其实都是同一族的对象,有相似的属性与行为,同时,员工之间又有层级结构,很适合用组合模式来表示员工之间的关系:

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
public class Test {
    public static void main(String[] args) {
        // 构建层级结构
        Director director = new Director("Polly");
        Manager frontendManager = new Manager("Lilei");
        Manager backendManager = new Manager("Hanmeimei");
        Engineer jsEngineer = new Engineer("Lily");
        Engineer ueDesigner = new Engineer("Lucy");
        Engineer javaEngineer = new Engineer("Jim");
        Engineer dbAdmin = new Engineer("Kate");
        director.addChild(frontendManager);
        director.addChild(backendManager);
        frontendManager.addChild(jsEngineer);
        frontendManager.addChild(ueDesigner);
        backendManager.addChild(javaEngineer);
        backendManager.addChild(dbAdmin);

        // 如对待整体如对待单个对象一般
        director.work();
    }
}

// 定义了Employee的公共方法,同时定义了添加/删除节点的方法
public interface Employee<T extends Employee> {
    String getName();
    Collection<T> getChildren() throws OperationNotSupportedException;
    void addChild(T employee) throws OperationNotSupportedException;
    void removeChild(T employee) throws OperationNotSupportedException;
    void work();
}
public class Director implements Employee<Manager> {
    private String name;
    private Collection<Manager> children = new ArrayList<>();
    Director(String name) {
        this.name = name;
    };
    @Override
    public String getName() {
        return name;
    }
    @Override
    public Collection getChildren() {
        return children;
    }
    @Override
    public void addChild(Manager employee) {
        children.add(employee);
    }
    @Override
    public void removeChild(Manager employee) {
        children.remove(employee);
    }
    // 循环调用子节点的方法
    @Override
    public void work() {
        System.out.println("Director " + name + " gives command to his subordinate...");
        for (Employee child : children) {
            child.work();
        }
    }
}
public class Manager implements Employee<Engineer> {
    private String name;
    private Collection<Engineer> children = new ArrayList<>();
    Manager(String name) {
        this.name = name;
    };
    @Override
    public String getName() {
        return name;
    }
    @Override
    public Collection getChildren() {
        return children;
    }
    @Override
    public void addChild(Engineer employee) {
        children.add(employee);
    }
    @Override
    public void removeChild(Engineer employee) {
        children.remove(employee);
    }
    // 循环调用子节点的方法
    @Override
    public void work() {
        System.out.println("Manager " + name + " gives command to his subordinates...");
        for (Employee child : children) {
            child.work();
        }
    }
}
public class Engineer implements Employee {
    private String name;
    Engineer(String name) {
        this.name = name;
    };
    @Override
    public String getName() { 
        return name; 
    }
    @Override
    public Collection getChildren() throws OperationNotSupportedException {
        throw new OperationNotSupportedException("No child under engineer.");
    }
    @Override
    public void addChild(Employee employee) throws OperationNotSupportedException {
        throw new OperationNotSupportedException("Can not add child for engineer.");
    }
    @Override
    public void removeChild(Employee employee) throws OperationNotSupportedException {
        throw new OperationNotSupportedException("Can not remove child for engineer.");
    }
    @Override
    public void work() {
        System.out.println("Engineer " + name + " is coding...");
    }
}

输出:

1
2
3
4
5
6
7
Director Polly gives command to his subordinate...
Manager Lilei gives command to his subordinates...
Engineer Lily is coding...
Engineer Lucy is coding...
Manager Hanmeimei gives command to his subordinates...
Engineer Jim is coding...
Engineer Kate is coding...

uml

运用组合模式,我们可以用一致的行为来操作整体与单个对象,显著降低了系统的复杂度。同时,树形结构也很易于扩展,方便日后的维护。

java.awt.Container就运用了组合模式,各个组件之间,其实是个树形结构,Container是所有组件的根结点:

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
public class Container extends Component {
    private java.util.List<Component> component = new ArrayList<>();
    // 添加组件
    protected void add(Component comp, Object constraints, int index) {
        ...
        //index == -1 means add to the end.
        if (index == -1) {
            component.add(comp);
        } else {
            component.add(index, comp);
        }
        comp.parent = this;
        comp.setGraphicsConfiguration(thisGC);
        ...
    }
    // 移除组件
    public void remove(int index) {
        synchronized (getTreeLock()) {
            ...
            component.remove(index);
            comp.setGraphicsConfiguration(null);
            ...
        }
    }
    // 调用updateGraphicsData方法,以及子节点的updateGraphicsData方法
    boolean updateGraphicsData(GraphicsConfiguration gc) {
        boolean ret = super.updateGraphicsData(gc);
        for (Component comp : component) {
            if (comp != null) {
                ret |= comp.updateGraphicsData(gc);
            }
        }
        return ret;
    }
    ...
}

4 总结

当对象之间存在层级关系的时候,可以考虑使用组合模式,统一单个对象和组合对象,降低系统复杂度。

文中例子的github地址