复合模式是SOLID吗?

时间:2009-10-16 18:03:59

标签: design-patterns composite solid-principles liskov-substitution-principle interface-segregation-principle

复合模式中的Leaf实现了Component接口,包括Leaf永远不会使用的AddRemoveGetChild方法。这似乎违反了接口隔离原则。

复合模式SOLID的用法是什么?

链接到复合图案:http://www.dofactory.com/Patterns/PatternComposite.aspx

4 个答案:

答案 0 :(得分:10)

您的链接和大多数图书中描述的模式中的真实气味是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具有合成方法,并且甚至没有向大多数客户公开它有子项的事实(在此示例中,客户端只需要来自某个位置的军队列表 - 事实它们位于许多子位置是无关紧要的。)

请记住,设计模式不一定是您必须完全实现的。把它们想象成食谱。当你在烹饪时遵循食谱时,你当然可以完全遵循它。然而,一些厨师会在食谱上投入自己的曲折。其他人甚至不会看它,因为他们是专家,可以根据食谱的精神一起扔东西,甚至不用考虑它。设计模式也是如此。它们是可塑性的食谱。

您也可以采用那些SOLID原则。如果您阅读罗伯特·马丁的文章,他表示不经过深思熟虑地应用这些原则会产生过于复杂的代码。软件是通过一系列权衡和平衡设计的 - 有时你放弃纯粹的SOLID,因为它产生更简洁,更简单的代码。如果你要使代码完美地封装,灵活,解耦等,你将发明一种新的编程语言: - )

答案 1 :(得分:6)

我会说你的链接中描述的复合模式违反了Liskov substitution principle,这是SOLID原则中的一个。

Component的方法仅对Composite有意义,例如Add()Leaf继承自Component,因此它会像任何其他Add()一样拥有Component方法。但是Leafs没有子节点,因此以下方法调用无法返回有意义的结果:

myLeaf.Add( someChild );

该调用必须抛出MethodNotSupportedException,返回null或以其他方式向调用者表明将子项添加到Leaf是没有意义的。

因此,您无法像其他任何Leaf那样对待Component,因为如果您尝试,您将获得例外。 Liskov替代原则指出:

  

设q(x)是可证明的属性   类型为T的对象x然后q(y)应该   对于S类型的对象y来说是真的   S是T的子类型。

Components具有可以向其添加子项的属性。但是,您无法将儿童添加到Leaf,即使Leaf是[{1}}的子类型,但这违反了原则。

答案 2 :(得分:2)

GoF书第167页专门解决了此问题。

  

尽管Composite类实现了AddRemove操作来管理子代,但是Composite模式中的一个重要问题是声明了哪些类这些操作...我们应该在Component中声明这些操作并使它们对Leaf类有意义,还是应该仅在Composite中声明和定义它们?

     

该决定涉及安全性和透明度之间的权衡:

     
      
  • 在类层次结构的根部定义子级管理界面可提高透明度,因为您可以统一对待所有组件。但是,这样做会增加您的安全性,因为客户可能会尝试执行无意义的操作,例如从叶子中添加和删除对象。
  •   
  • 在Composite类中定义子级管理可以提高安全性,因为任何在叶中添加或删除对象的尝试都将在编译时以静态类型的语言(如C ++)捕获。但是您会失去透明度,因为叶子和复合材料具有不同的界面。
  •   
     

在这种模式下,我们强调了安全性的透明性。

最后一句话是承认该模式违反了类型安全原则。就SOLID而言,这主要是LSP,但也可能是ISP。请注意,“ 声明子类不使用的方法”是对ISP的过度简化。这些未使用方法的真正危险在于,它们将需要子类不需要的其他依赖关系,从而增加了模块之间的耦合。

答案 3 :(得分:0)

使用Composite,您可以统一处理所有对象,并可能摆脱&#34; instanceOf&#34;这是代码气味的明显标志。所以它乍一看就是对LSP(Liskov's)的尊重。但是,当您区分常见方法实现时,它可能会开始违反LSP。所以,IMO,我们需要保持这种平衡。