利斯科夫替补和组成

时间:2009-02-16 18:42:16

标签: c# .net composition liskov-substitution-principle

假设我有一个这样的课程:

public sealed class Foo
{
    public void Bar
    {
       // Do Bar Stuff
    }
}

我想扩展它以增加扩展方法可以做的事情....我唯一的选择是组合:

public class SuperFoo
{
    private Foo _internalFoo;

    public SuperFoo()
    {
        _internalFoo = new Foo();        
    }

    public void Bar()
    {
        _internalFoo.Bar();
    }

    public void Baz()
    {
        // Do Baz Stuff
    }
}

虽然这很有效,但它仍然有很多工作......但是我仍然遇到了一个问题:

  public void AcceptsAFoo(Foo a)

我可以在这里传递一个Foo,但不是超级Foo,因为C#不知道SuperFoo真的符合Liskov替换意义......这意味着我的扩展类通过组合使用非常有限。 / p>

因此,解决这个问题的唯一方法是希望原始API设计人员留下一个界面:

public interface IFoo
{
     public Bar();
}

public sealed class Foo : IFoo
{
     // etc
}

现在,我可以在SuperFoo上实现IFoo(因为SuperFoo已经实现了Foo,只需要更改签名)。

public class SuperFoo : IFoo

在完美的世界中,消耗Foo的方法将消耗IFoo:

public void AcceptsAFoo(IFoo a)

现在,由于常见的界面,C#了解SuperFoo和Foo之间的关系,一切都很好。

最大的问题是.NET密封了许多偶尔会很好扩展的类,而且它们通常不会实现通用接口,因此采用Foo的API方法不会接受SuperFoo而你无法添加超载。

所以,对于那里的所有作曲迷们......你如何解决这个限制?

我唯一能想到的就是公开展示内部Foo,以便你偶尔可以传递它,但这看起来很混乱。

2 个答案:

答案 0 :(得分:14)

在我开始研究自己的可重用库之前,我发现自己也在问同样的问题。很多时候,如果不需要来自实现者的模糊或神秘的调用序列,那么您最终无法扩展某些类。

当允许扩展您的类时,您必须要问:如果开发人员扩展了我的类,并将这个新类传递给我的库,我是否可以透明地使用这个新类?我可以在这个新课上正常工作吗?这个新课程真的会表现得一样吗?

我发现大多数时候.Net Framework中的密封类都有一些你不知道的底层需求,并且鉴于当前的实现不能安全地暴露给子类。 / p>

这并不完全回答您的问题,但它提供了有关为什么不能在.Net Framework中继承所有类的见解(以及为什么您应该接受密封您的某些类)。

答案 1 :(得分:3)

我担心简短的回答是,你不能不做必要的事情,即改为传递组合的实例变量。

可以允许隐式或显式转换为该类型(其实现只是传递组合实例)但是这样,IMO会非常邪恶。

sixletter变量的答案是好的,我不会重复它,但如果你指出你希望扩展哪些课程,我们可能会告诉你他们为什么要阻止它。