设计模式-组合模式

1,432 阅读4分钟

# 七大软件设计原则
# 设计模式-工厂模式
# 设计模式-单例模式
# 设计模式-原型模式
# 设计模式-策略模式
# 设计模式-责任链模式
# 设计模式-建造者模式
# 设计模式-深度分析代理模式
# 设计模式-门面模式
# 设计模式-装饰器模式
# 设计模式-享元模式

组合模式(Composite Pattern ) 也称为整体-部分(Part-Whole)模式,它的宗旨是通过将单个 对象(叶子节点)和组合对象(树枝节点)用相同的接口进行表示,使得客户对单个对象和组合对象的 使用具有一致性,属于结构型模式。

组合模式的适用场景

当子系统与其内各个对象层次呈现树形结构时,可以使用组合模式让子系统内各个对象层次的行为 操作具备一致性。客户端使用该子系统内任意一个层次对象时,无须进行区分,直接使用通用操作即可, 为客户端的使用带来了便捷。

  1. 希望客户端可以忽略组合对象与单个对象的差异时
  2. 对象层次具备整体和部分,呈树形结构。

组合模式的通用UML图

这里主要分为透明模式和安全模式:

透明模式

在抽象类中包含了叶子节点和树枝节点的所有方法,也就是对于叶子节点来说它是能看到树枝节点的一些方法的所以对于叶子节点来说事透明的具体UML类图如下: image.png 这里有个小细节我们可以看一下抽象类的代码: image.png 发现我这边定义的并不是抽象方法而是方法的实现。
这样做的主要好处是子类不需要的方法没必要非要去实现,只覆盖自己需要的方法即可

安全模式

image.png

代码示例

比如公司的部门,每个部门下面还可以在创建部门,部门下面可以有子部门也可以有员工
实现代码如下:

public class Department extends DepartmentComponent{

    private Integer level;

    private List<DepartmentComponent> dept = new ArrayList<>();

    public Department(String name,Integer level) {
        super(name);
        this.level = level;
    }

    @Override
    void addChild(DepartmentComponent component) {
        dept.add(component);
    }

    @Override
    void removeChild(DepartmentComponent component) {
        dept.remove(component);
    }

    @Override
    void print() {
        System.out.println("部门-"+super.getName());
        for (DepartmentComponent component : dept){
            if(this.level != null){
                for(int i = 0; i < level; i++){
                    System.out.print("--");
                }
            }
            component.print();
        }
    }
}
public abstract class DepartmentComponent {

    private final String name;
    public DepartmentComponent(String name){
        this.name = name;
    }

    void print(){
        throw new UnsupportedOperationException("不支持操作");
    }

    String getName(){
        return this.name;
    }
    void addChild(DepartmentComponent component){
        throw new UnsupportedOperationException("不支持操作");
    }
    void removeChild(DepartmentComponent component){
        throw new UnsupportedOperationException("不支持操作");
    }
}
public class Staff extends DepartmentComponent{
    public Staff(String name) {
        super(name);
    }

    @Override
    void print() {
        System.out.println("员工:"+ super.getName());
    }
}
public class Test {
    public static void main(String[] args) {
        Department department1 = new Department("土豆1", 1);
        Department department2 = new Department("土豆1-1", 2);
        Department department3 = new Department("土豆1-2", 2);

        Staff staff = new Staff("张三");
        Staff staff1 = new Staff("张三2");
        Staff staff2 = new Staff("张三3");
        Staff staff3 = new Staff("张三4");
        Staff staff4 = new Staff("张三5");
        Staff staff5 = new Staff("张三6");

        department1.addChild(department2);
        department1.addChild(department3);
        department1.addChild(staff);
        department2.addChild(staff1);
        department2.addChild(staff2);
        department2.addChild(staff3);
        department2.addChild(staff4);
        department3.addChild(staff5);
        department1.print();

    }
}

image.png

组合模式在源码中的应用

HashMap实现了Map接口的,Map就是组合模式中的抽象根节点 image.png 我们再看一下 putAll方法 image.png

final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
    int s = m.size();
    if (s > 0) {
        if (table == null) { // pre-size
            float ft = ((float)s / loadFactor) + 1.0F;
            int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                     (int)ft : MAXIMUM_CAPACITY);
            if (t > threshold)
                threshold = tableSizeFor(t);
        } else {
            // Because of linked-list bucket constraints, we cannot
            // expand all at once, but can reduce total resize
            // effort by repeated doubling now vs later
            while (s > threshold && table.length < MAXIMUM_CAPACITY)
                resize();
        }

        for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
            K key = e.getKey();
            V value = e.getValue();
            putVal(hash(key), key, value, false, evict);
        }
    }
}

在看一下putVal方法

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

发现 HashMap 是一个中间构件,HashMap 中的 Node 节点就是叶子节点。说到中间 构件就会有规定的存储方式。HashMap 中的存储方式是一个静态内部类的数组 Node<K,V>[] tab, image.png image.png

组合模式优缺点

优点:

  • 组合模式可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,它让客户端忽略了层次的差异,方便对整个层次结构进行控制。
  • 客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端代码
  • 在组合模式中增加新的树枝节点和叶子节点都很方便,无须对现有类库进行任何修改,符合“开闭原则”。
  • 组合模式为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子节点和树枝节点的递归组合,可以形成复杂的树形结构,但对树形结构的控制却非常简单

缺点:

  • 使用设计变得更加抽象
  • 限制类型时, 比较复杂 ;比如上述的例子如果部门下面只能添加员工,使用组合模式时,又不能依赖类型系统施加约束,它们都来自于节点的抽象层;在这种情况下,必须通过在运行时进行类型检查, 这样就变得比较复杂;