C# 理解Liskov替换原理

C# 理解Liskov替换原理,c#,solid-principles,C#,Solid Principles,我试图通过一遍又一遍地阅读条目来确定我对上述原则的理解 撇开仍然让我悲伤的协变和逆变的概念不谈,wikipedia还提到超类型的不变量必须保留在子类型和历史约束或历史规则中。基于最后两个概念,我提出了一个小例子: class Program { static void Main(string[] args) { var fooUser = new FooUser(); var fooBase = new FooBase("Serge");

我试图通过一遍又一遍地阅读条目来确定我对上述原则的理解

撇开仍然让我悲伤的协变和逆变的概念不谈,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();
    }
}
所以我的问题是:基于上述两个概念,我在这个例子中是否违反了原则?若否,原因为何


非常感谢您。

您在这里似乎没有违反LSP。我留下一个小小的怀疑窗口,因为理论上我们对
FooBase
的不变量一无所知,但是对这些结果进行合理的猜测,看不到明显的问题

假设不变量很好,那么历史原则就很重要了,派生类允许
Name
的值在基类不允许的对象生命周期内发生更改。如果不是因为一个小细节:
Name
有一个
protected
setter,那么这显然违反了LSP


受保护的setter应该意味着
FooBase
的作者期望派生类在对象的生存期内更改
Name
的值,即使基类没有这样做。将此与受保护的字段名称进行对比,该字段不能具有不同的访问级别来获取和设置其值。

您在此处似乎没有违反LSP。我留下一个小小的怀疑窗口,因为理论上我们对
FooBase
的不变量一无所知,但是对这些结果进行合理的猜测,看不到明显的问题

假设不变量很好,那么历史原则就很重要了,派生类允许
Name
的值在基类不允许的对象生命周期内发生更改。如果不是因为一个小细节:
Name
有一个
protected
setter,那么这显然违反了LSP


受保护的setter应该意味着
FooBase
的作者期望派生类在对象的生存期内更改
Name
的值,即使基类没有这样做。将此与受保护的字段名称进行对比,该字段不能具有不同的访问级别来获取和设置其值。

该示例还不够健壮,部分原因是C#没有一种干净的方式来表示类不变量。或者,更确切地说,如果有,我不知道


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

这个例子还不够健壮,部分原因是C#没有一种干净的方式来表示类不变量。或者,更确切地说,如果有,我不知道


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

要违反LSP,您需要一个客户机类,该类对类接口进行一些假设。这个假设不能用一种正式的方式表达,有时它只是来自于使用的上下文

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

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


这种对上下文的需求还意味着两个类可以在一个客户机上下文中违反LSP,而相同的类可能在其他上下文中不违反LSP

要违反LSP,您需要一个客户机类,该类对类接口进行一些假设。这个假设不能用一种正式的方式表达,有时它只是来自于使用的上下文

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

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


这种对上下文的需求还意味着两个类可以在一个客户机上下文中违反LSP,而相同的类可能在其他上下文中不违反LSP

LSP冲突本质上是一种冲突,如果
1.子类型违反了超类型的不变量或
2.超级类型的前提条件通过子类型或
3.子类型会削弱Post条件

在接口设计时,上述三个条件必须通过正式的“合同设计”预先确定
在您的示例中,FooBase没有定义任何要遵守的正式规则