让我们考虑以下示例.我有这样的类的层次结构:
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{foo, bar}; HandleItems(items); } static void HandleItems(IEnumerable 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 ='true'其他'false'.
当然,在HandleItems()中以不同方式处理项目是设计错误和Liskov替换原则违规的标志.
你会建议什么方法或模式摆脱这种代码气味?
如果已经提出过类似的问题,我很抱歉.
您的代码不违反Liskov替换原则.该原则仅表明所有子类型必须以兼容的方式运行,而不会在注入不同的实现时破坏消费者.在你的例子中.当您提供不同类型时,代码不会中断.
然而,向下转换Bar
是一种代码气味,因为HandleItems
违反了依赖性倒置原则,因为HandleItems
现在依赖于具体类型而不是抽象.此外,此代码可能导致稍后打开/关闭原则违规,因为HandleItems
每次Base
添加新子类型时可能需要更改方法.一旦您需要更改HandleItems
它意味着它不会被关闭进行修改.
然而,你的例子是抽象的,这使得很难给出一些精确的反馈,但总的来说,我会说你应该将设置的责任ShouldDoSomethingElse
转移给调用者,例如:
var foo = new Foo(); var bar = new Bar { ShouldDoSomethingElse = true }; var items = new List{ foo, bar }; HandleItems(items);
这可以防止HandleItems
必须知道有关派生类型的任何信息(允许派生类型单独部署)并防止HandleItems
不断变化.