Skip to content

Head First 设计模式 11-组合模式

🏷️ 《Head First 设计模式》

组合模式 允许你将对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。

组合模式让我们能用 树形方式 创建对象的结构,树里面包含了组合以及个别的对象。

使用组合结构,我们能把 相同的操作 应用在 组合个别对象 上。换句话说,在大多数情况下,我们可以忽略对象组合和个别对象之间的差别。

之所以基类(Component)是抽象类(abstract class),是因为树形结构中 叶节点(Leaf)只需要实现具体的操作,而 子节点(Composite)则要实现所有的方法。同时,为了防止意外的访问了叶节点不应支持的方法,基类中虚方法的默认实现中抛出了 InvalidOperationException

示例代码

Component

csharp
public abstract class Component
{
    public virtual void Operation()
    {
        throw new InvalidOperationException();
    }

    public virtual void Add(Component component)
    {
        throw new InvalidOperationException();
    }

    public virtual void Remove(Component component)
    {
        throw new InvalidOperationException();
    }

    public virtual Component GetChild(int index)
    {
        throw new InvalidOperationException();
    }
}

Leaf

csharp
public class Leaf : Component
{
    public override void Operation()
    {
        Console.WriteLine("a leaf do some operation.");
    }
}

Composite

csharp
public class Composite : Component
{
    List<Component> _components = new List<Component>();

    public override void Add(Component component)
    {
        _components.Add(component);
    }

    public override Component GetChild(int index)
    {
        return _components[index];
    }

    public override void Operation()
    {
        Console.WriteLine("a composite do some operation.");
        foreach (var component in _components)
        {
            component.Operation();
        }
    }

    public override void Remove(Component component)
    {
        _components.Remove(component);
    }
}

Client

csharp
Composite subComposite1 = new Composite();
subComposite1.Add(new Leaf());
subComposite1.Add(new Leaf());

Composite subComposite2 = new Composite();
subComposite2.Add(new Leaf());

Composite composite = new Composite();
composite.Add(subComposite1);
composite.Add(subComposite2);

composite.Operation();

设计原则

本节没有新的设计原则,但是却破坏了上一节中出现的 单一职责 原则。 Composite 类兼具两种类型的操作:迭代 和 叶节点的业务操作,可能会导致叶节点错误的执行了子节点的操作,失去了一些“安全性”。这是设计上的抉择,牺牲了安全性,增加了透明性

什么是透明性? 通过让组件的接口同时包含一些管理子节点和叶节点的操作,客户就可以将子节点和叶节点一视同仁。也就是说,一个元素究竟是组合还是叶节点,对客户来说是透明的。

尽管我们受到设计原则的指导,但是,我们总是需要观察某原则对我们的设计所造成的影响。有时候,我们会故意做一些违反原则的事情。

组合迭代器

如果需要遍历树结构的所有节点,就需要实现一个 组合迭代器CompositeEnumerator) ,另外在叶节点上还会用到 空迭代器NullEnumerator) 。

需要 Componenet 类继承 IEnumerable<Component> 接口, CompositeLeaf 需要返回各自的迭代器。

其类图如下:

Component

csharp
public abstract class Component: IEnumerable<Component>
{
    public virtual void Operation()
    {
        throw new InvalidOperationException();
    }

    public virtual void Add(Component component)
    {
        throw new InvalidOperationException();
    }

    public virtual void Remove(Component component)
    {
        throw new InvalidOperationException();
    }

    public virtual Component GetChild(int index)
    {
        throw new InvalidOperationException();
    }

    public virtual IEnumerator<Component> GetEnumerator()
    {
        throw new NotImplementedException();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        throw new NotImplementedException();
    }
}

Leaf

csharp
public class Leaf : Component
{
    public override void Operation()
    {
        Console.WriteLine("a leaf do some operation.");
    }

    public override IEnumerator<Component> GetEnumerator()
    {
        return new NullEnumerator();
    }
}

Composite

csharp
public class Composite : Component
{
    List<Component> _components = new List<Component>();

    public override void Add(Component component)
    {
        _components.Add(component);
    }

    public override Component GetChild(int index)
    {
        return _components[index];
    }

    public override void Operation()
    {
        Console.WriteLine("a composite do some operation.");
        foreach (var component in _components)
        {
            component.Operation();
        }
    }

    public override void Remove(Component component)
    {
        _components.Remove(component);
    }

    public override IEnumerator<Component> GetEnumerator()
    {
        return _components.GetEnumerator();
    }
}

CompositeEnumerator

csharp
public class CompositeEnumerator : IEnumerator<Component>
{
    Stack<IEnumerator<Component>> _stack = new Stack<IEnumerator<Component>>();
    private List<Component> _components;

    public CompositeEnumerator(List<Component> components)
    {
        _components = components;
        _stack.Push(_components.GetEnumerator());
    }
    public Component Current { get; private set; }
    object IEnumerator.Current => Current;

    public void Dispose()
    {
        _stack.Clear();
        _stack = null;
        _components = null;
    }

    public bool MoveNext()
    {
        if (!_stack.TryPeek(out var enumerator))
        {
            return false;
        }
        if (!enumerator.MoveNext())
        {
            _stack.Pop();
            return MoveNext();
        }
        else
        {
            Current = enumerator.Current;
            if (Current is Composite)
            {
                _stack.Push(Current.GetEnumerator());
            }
            return true;
        }
    }

    public void Reset()
    {
        _stack.Clear();
        _stack.Push(_components.GetEnumerator());
    }
}

NullEnumerator

csharp
public class NullEnumerator : IEnumerator<Component>
{
    public Component Current => default(Component);

    object IEnumerator.Current => Current;

    public void Dispose()
    {
    }

    public bool MoveNext()
    {
        return false;
    }

    public void Reset()
    {
    }
}

Client

csharp
Composite subComposite1 = new Composite();
subComposite1.Add(new Leaf());
subComposite1.Add(new Leaf());

Composite subComposite2 = new Composite();
subComposite2.Add(new Leaf());
subComposite2.Add(subComposite1);

Composite composite = new Composite();
composite.Add(subComposite2);

List<Composite> composites = new List<Composite>();
composites.Add(composite);

foreach (var item in composites)
{
    item.Operation();
}