如何处理复合模式中的添加、删除功能?
How to handle functions for add, delete in composite pattern?
Composite pattern
对于处理部分-整体层次结构很有用。它有一个 Component
的接口。 Leaf
和 Composite
都提供了 Component
接口的实现。
Leaf
class 中的 Add(Component c)
、Remove(Component c)
和 GetChild(int position)
方法应该如何实现?
我可以让方法什么也不做,或者抛出异常,例如:OperationNotSuportedByLeafException
。 但是这样做会破坏Liskov替换原则。处理这些方法的最佳方式是什么?
编辑:另一种方法是在 Composite 中移动这些方法。它将是暴露的最顶层接口,即 Component。移动 Composite 中的方法将需要显式转换,这时需要调用 add
、remove
操作,这又违反了良好的设计原则。
有两种方法可以解决这个问题。
当然取决于你的设计目标是什么。如果您的目标是 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
);
Composite pattern
对于处理部分-整体层次结构很有用。它有一个 Component
的接口。 Leaf
和 Composite
都提供了 Component
接口的实现。
Leaf
class 中的 Add(Component c)
、Remove(Component c)
和 GetChild(int position)
方法应该如何实现?
我可以让方法什么也不做,或者抛出异常,例如:OperationNotSuportedByLeafException
。 但是这样做会破坏Liskov替换原则。处理这些方法的最佳方式是什么?
编辑:另一种方法是在 Composite 中移动这些方法。它将是暴露的最顶层接口,即 Component。移动 Composite 中的方法将需要显式转换,这时需要调用 add
、remove
操作,这又违反了良好的设计原则。
有两种方法可以解决这个问题。
当然取决于你的设计目标是什么。如果您的目标是 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
);