2015-12-22 103 views
0

让我们考虑下面的例子。我有类的这样的层次:避免违反LSP的最佳方法

abstract class Base 
{ 
    public abstract void DoSomething(); 
} 

class Foo : Base 
{ 
    public override void DoSomething() 
    { 
     Console.WriteLine("Foo. DoSomething..."); 
    } 
} 

class Bar : Base 
{ 
    public override void DoSomething() 
    { 
     Console.WriteLine("Bar. DoSomething..."); 

     if (ShouldDoSomethingElse) 
     { 
      DoSomethingElse(); 
     } 
    } 

    public void DoSomethingElse() 
    { 
     Console.WriteLine("Bar. DoSomething else..."); 
    } 

    public bool ShouldDoSomethingElse { get; set; } 
} 

和我有客户这样的事情:

class Program 
{ 
    static void Main(string[] args) 
    { 
     var foo = new Foo(); 
     var bar = new Bar(); 
     var items = new List<Base> {foo, bar}; 

     HandleItems(items); 
    } 

    static void HandleItems(IEnumerable<Base> items) 
    { 
     foreach (var item in items) 
     { 
      if (item is Bar) 
      { 
       //Code smell! LSP violation. 
       var bar = item as Bar; 
       bar.ShouldDoSomethingElse = true; 
      } 
      item.DoSomething(); 
     } 
    } 
} 

请注意,我们可以有几个客户其中一些可能需要ShouldDoSomethingElse =“真”别人'虚假'。

当然,在HandleItems()中处理不同的项目是不好的设计和违反Liskov替换原则的标志。

你会建议什么方法或模式来摆脱这种代码味道?

如果已经提出类似问题,我很抱歉。

回答

5

您的代码不违反Liskov替代原则。该原则仅仅表明,所有的子类型必须以兼容的方式运行,而不会在注入不同的实现时破坏消费者。在你的例子中。当您提供不同的类型时,代码不会中断。

然而,沮丧到Bar是一种代码味道,因为HandleItems违反了依赖倒置原则,因为HandleItems现在取决于具体类型而不是抽象。此外,此代码稍后可能会导致违反打开/关闭原则,因为每次添加新的Base子类型时,您都可能需要更改HandleItems方法。一旦你需要改变HandleItems这意味着它不能修改。

但是你举的例子是抽象的,这使得它很难给一些精确的反馈,但一般我会说,你应该移动ShouldDoSomethingElse设置为来电者,如责任:

var foo = new Foo(); 
var bar = new Bar { ShouldDoSomethingElse = true }; 
var items = new List<Base> { foo, bar }; 

HandleItems(items); 

这可以防止HandleItems必须知道派生类型的任何内容(允许派生类型单独部署)并防止HandleItems不断变化。

1

如果你想要取决于客户端的不同行为,让客户端传递一个参数给DoSomething,告诉方法该做什么。或者创建两种不同的方法,并将其留给客户端调用。

我不确定我是否理解这个问题。您使用多态性,然后通过确定类型并基于该知识进行操作来使其变得毫无意义。 DoSomething应该已经做了一些类特定的事情,多态的要点是让客户端不知道不同的实现并且仍然得到适当的行为。

相关问题