复合模式中的叶子实现了组件接口,包括Add
叶子永远不会使用的方法Remove
。GetChild
这似乎违反了接口隔离原则。
那么 Composite Pattern SOLID的用法是什么?
复合模式链接:http: //www.dofactory.com/Patterns/PatternComposite.aspx
复合模式中的叶子实现了组件接口,包括Add
叶子永远不会使用的方法Remove
。GetChild
这似乎违反了接口隔离原则。
那么 Composite Pattern SOLID的用法是什么?
复合模式链接:http: //www.dofactory.com/Patterns/PatternComposite.aspx
如您的链接和大多数书籍中所描述的模式中的真正气味是Component
具有Composite
. 我认为这可能是因为该模式相当古老,并且多年来一直以这种方式重复。我的看法是,只有Composite
应该有任何与合成相关的方法。
我曾经将棋盘游戏转换为电脑游戏。棋子被放置在地球地图上,分成六边形。99% 的六边形代表一个位置。不幸的是,一些六边形包含多个位置,例如,一些六边形内部有几个岛屿。我使用复合模式来表示这些位置,但不像您的链接上描述的那样。它是这样的(在 Java 中):
public interface Location {
Set<Army> getArmies();
}
public class SingleLocation implements Location {
public Set<Army> getArmies() {
return armies ;
}
private Set<Army> armies = new HashSet<Army>();
}
public class CompositeLocation implements Location {
public Set<Army> getArmies() {
Set<Army> armies = new HashSet<Army>();
for(Location subLocation: subLocations) {
armies.addAll(subLocation.getArmies());
}
return armies;
}
public void addSubLocation(Location location) {
subLocations.add(location);
}
private Set<Location> subLocations = new HashSet<Location>();
}
请注意,只有Composite
compositing 方法,甚至没有向大多数客户端公开它有孩子的事实(在这个例子中,客户端只想要一个位置的军队列表 - 他们在许多子位置的事实无关紧要)。
请记住,设计模式并不是您必须完全实现的固定不变的东西。把它们想象成食谱。当您在烹饪时遵循食谱时,您当然可以完全遵循它。然而,一些厨师会在食谱上加入自己的曲折。其他人甚至不会看它,因为他们是专家,甚至可以不假思索地按照食谱的精神把一些东西放在一起。设计模式也是如此。它们是可延展的食谱。
您也可以将这些 SOLID 原则走得太远。如果您阅读 Robert Martin 的文章,他会指出,不加思索地全面应用这些原则会产生过于复杂的代码。软件是通过一系列权衡和平衡来设计的——有时你会放弃纯 SOLID,因为它会产生更简洁、更简单的代码。如果您要使您的代码完美封装、灵活、解耦等,您将发明一种新的编程语言:-)
我想说的是,您的链接中描述的 Composite 模式违反了Liskov 替换原则,这是五项SOLID原则之一。
Component
Composite
具有仅对例如有意义的方法Add()
。Leaf
继承自,Component
因此它将具有Add()
与其他任何Component
. 但是Leafs
没有孩子,所以下面的方法调用不能返回有意义的结果:
myLeaf.Add( someChild );
该调用必须抛出 a MethodNotSupportedException
,返回null
或以其他方式向调用者表明将子级添加到 aLeaf
没有意义。
因此,您不能Leaf
像对待任何其他人一样对待,Component
因为如果您尝试这样做,您会得到一个例外。Liskov 替换原则指出:
令 q(x) 是关于 T 类型的对象 x 的可证明性质。那么 q(y) 对于 S 类型的对象 y 应该为真,其中 S 是 T 的子类型。
Components
拥有可以向他们添加孩子的属性。但是你不能将孩子添加到 aLeaf
中,即使Leaf
是 的子类型Component
,这违反了原则。
GoF 书在第 167 页专门解决了这个问题。
虽然 Composite 类实现了管理子类的
Add
andRemove
操作,但是 Composite 模式中的一个重要问题是哪些类声明了这些操作……我们应该在 Component 中声明这些操作并使它们对 Leaf 类有意义,还是应该声明和定义他们只在复合?该决定涉及安全性和透明度之间的权衡:
- 在类层次结构的根部定义子管理接口为您提供了透明度,因为您可以统一对待所有组件。但是,这会损害您的安全,因为客户可能会尝试做一些无意义的事情,例如从叶子中添加和删除对象。
- 在 Composite 类中定义子管理可以为您提供安全性,因为任何从叶子中添加或删除对象的尝试都将在编译时被静态类型语言(如 C++)捕获。但是你会失去透明度,因为树叶和复合材料有不同的界面。
在这种模式下,我们强调了透明度而不是安全性。
最后一句承认该模式违反了类型安全原则。就 SOLID 而言,这主要是 LSP,但也可能是 ISP。请注意,“声明子类不使用的方法”是对 ISP 的过度简化。那些未使用的方法的真正危险在于它们将需要子类不需要的额外依赖项,从而增加模块之间的耦合。
使用 Composite 可以让您统一处理所有对象,并可能摆脱“instanceOf”事物,这是代码异味的明显标志。因此,乍一看,它尊重 LSP(Liskov's)。但是,当您区分常用方法实现时,它可能会开始违反 LSP。所以,IMO,我们需要保持这种平衡。