理解Liskov替代原则

时间:2012-07-13 14:54:13

标签: c# solid-principles

我试图通过阅读wikipedia条目来确定对上述原则的理解。

除了仍然让我感到悲伤的协方差和逆变的概念之外,维基百科还提到超类型的不变量必须保留在子类型和历史约束或历史规则中。基于这最后两个概念,我提出了这个小例子:

class Program
{
    static void Main(string[] args)
    {
        var fooUser = new FooUser();

        var fooBase = new FooBase("Serge");

        var fooDerived = new FooDerived("Serge");

        fooUser.Use(fooBase); //will print "Serge"
        fooUser.Use(fooDerived); //will print "New User"

        Console.ReadKey();
    }
}

public class FooUser
{
    public void Use(IFoo foo)
    {
        foo.DoSomething();
        Console.WriteLine(foo.Name);
    }
}

public interface IFoo
{
    string Name { get; }
    void DoSomething();
}

public class FooBase : IFoo
{
    public string Name { get; protected set; }

    public FooBase(string name)
    {
        Name = name;
    }

    public virtual void DoSomething()
    {
    }
}

public class FooDerived : FooBase
{
    public FooDerived(string name) : base(name)
    {
    }

    public override void DoSomething()
    {
        Name = "New Name";

        base.DoSomething();
    }
}

所以我的问题是:基于上面提到的两个概念,我是否违反了这个例子的原则?如果没有,为什么?

非常感谢你。

4 个答案:

答案 0 :(得分:3)

要违反LSP,您需要一个客户端类,它在类接口上做出一些假设。这种假设绝不能以正式的方式表达,有时它只是来自使用的背景。

假设你有一个可枚举的类,允许你添加元素。客户端的假设可以是例如,如果它添加N个元素,那么可以从集合中读取N个元素。然后从集合中派生一个集合,在添加时删除重复的元素。客户期望现在是错误的,因为即使添加了N个元素,有时也可以读取少于N个元素。

对我来说,违反LSP需要一个定义一些期望的上下文。由于您的代码没有任何期望,因此不会违反LSP。

这种上下文的需要还意味着两个类可以违反一个客户端上下文的LSP,而相同的类可能不会违反其他上下文中的LSP。

答案 1 :(得分:1)

您似乎没有违反LSP。我要离开一个小窗口怀疑,因为理论上我们对FooBase的不变量一无所知,但在没有明显问题的情况下对这些结果作出合理的猜测。

假设不变量很好,这就使得历史原则很重要,派生类允许Name的值在基类没有的对象的生命周期内发生变化。如果不是一个小细节,这肯定会违反LSP:Name有一个protected设置器。

受保护的setter应该意味着FooBase 的作者希望派生类在对象的生命周期内更改Name的值,即使基类没有发生在做这个。将其与protected字段name进行对比,后者无法获取和设置其值的不同访问级别。

答案 2 :(得分:0)

这个例子并不足以说明,部分原因是因为C#没有一种表达类不变量的简洁方法。或者,如果确实如此,我不知道它。

我说你没有违反不变量,因为FooBase没有保证Name不会改变或表示Name的允许值范围。恰恰相反 - 通过为Name包含受保护的setter,FooBase创建了一个期望,即可以通过派生类的内部机制来更改。

答案 3 :(得分:0)

LSP违规本质上是违规行为,如果有的话 1.子类型OR的违反超类型的不包括 2.超类型的前提条件由子类型OR加强 3. Sub类型削弱了后置条件。

上述三个条件必须事先决定,而界面设计则由正式的“合同设计”决定。
在您的示例中,FooBase没有定义任何扩展类要遵守的任何正式规则。
此外,name setter具有受保护的访问权限。因此我不认为LSP被违反。

但是

有时,如果针对测试用例分析LSP违规,也可能会有所帮助。

这个测试用例是否有意义:
fooUser.Use(fooBase); //将打印“Serge”
String nameDerived = fooUser.Use(fooDerived); //将打印“新用户”(假设这将返回名称)

断言(nameDerived等于“Serge”)

如果存在这样的测试用例(作为规范的必要条件),那么,显然上面的例子违反了LSP。

我的博客更多信息:http://design-principle-pattern.blogspot.in/2013/12/liskov-substitution-principle.html