如何处理复合模式中的添加、删除功能?

How to handle functions for add, delete in composite pattern?

Composite pattern 对于处理部分-整体层次结构很有用。它有一个 Component 的接口。 LeafComposite 都提供了 Component 接口的实现。

Leaf class 中的 Add(Component c)Remove(Component c)GetChild(int position) 方法应该如何实现?

我可以让方法什么也不做,或者抛出异常,例如:OperationNotSuportedByLeafException但是这样做会破坏Liskov替换原则。处理这些方法的最佳方式是什么?

编辑:另一种方法是在 Composite 中移动这些方法。它将是暴露的最顶层接口,即 Component。移动 Composite 中的方法将需要显式转换,这时需要调用 addremove 操作,这又违反了良好的设计原则。

有两种方法可以解决这个问题。

  1. 投掷OperationNotSuportedByLeafException。你可以讨论这是否会中断 LSP。我个人认为应该避免它,但有时它是最好的解决方案(例如,参见 Java 的不可变列表)。 LSP 是一项原则,旨在帮助您编写质量更高的代码。每个系统都有缺陷,这可能是其中之一。

  2. 将添加和删除移动到复合实体 (example)。这是您通常会在视图库中看到的内容。视图可以是文本块,也可以是包含许多其他视图的复杂布局。

当然取决于你的设计目标是什么。如果您的目标是 tree/graph 应该在任何时间点都可以修改,那么叶子实际上可以成为父节点。这种情况下,在组件中定义层级相关的方法就OK了。

另一种方法(虽然我不知道这是否适用于您的用例)是使用以下两个想法:

  • 使结构不可变
  • 将结构与函数分开

通过使结构不可变,我们可以将所有图形构造推给构造函数。这样我们就回避了你提到的类型转换问题,也让整个事情更容易推理。

通过将结构与功能分离,我们根本不需要向客户发布结构信息,而是提供我们想要提供的功能。有了这个我们就可以保留 Liskov、Demeter 和其他面向对象的东西。

这是它的样子:

public interface Node {
    // Here we offer the "functionality", but we don't
    // publish the "structure". This is made-up, I
    // don't know your use-case.
    void process(Consumer<Payload> payloadConsumer);
}

public class Leaf implements Node {
    private Payload payload;

    public Lead(Payload payload) {
        this.payload = payload;
    }

    @Override
    public void process(Consumer<Payload> payloadConsumer) {
        payloadConsumer.accept(payload);
    }
}

public class ParentNode implements Node {
    private List<Node> children;

    public ParentNode(Node... children) {
        this.children = asList(children);
    }

    // Here we implement the processing recursively.
    // All children can respond according to their semantics,
    // instead of assuming any structure beyond what we know.
    @Override
    public void process(Consumer<Payload> payloadConsumer) {
       children.forEach(child -> child.process(payloadConsumer));
    }
}

当然你可以定义你自己的 Node 类型,这取决于你想要表示什么样的逻辑。您可以定义多个操作,而不仅仅是我编写的一个 process() 方法。然后你可以像这样将所有这些连接在一起:

Node graph = new ParentNode(
   new ParentNode(new Leaf(p1), new Leaf(p2)),
   new SpecialLeaf(a, b, c) // Whatever
);