1

我试图通过反复阅读维基百科条目来确定我对上述原则的理解。

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

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

4 回答 4

3

要违反 LSP,您需要一个客户端类,该类对类接口做出一些假设。假设不能完全以正式的方式表达,有时它只是来自使用的上下文。

假设您有一个可枚举的类,它允许您添加元素。例如,客户端的假设可以是,如果它添加 N 个元素,则可以从集合中读取恰好 N 个元素。然后,您从您的集合中派生出一个集合,该集合在添加时会删除重复的元素。现在客户的期望是错误的,即使添加了 N 个元素,有时也可以读取 LESS THAN N 个元素。

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

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

于 2012-07-13T15:46:16.783 回答
1

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

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

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

于 2012-07-13T15:05:01.263 回答
0

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

我想说你没有违反不变量,因为 FooBase 没有保证 Name 不会改变或表示 Name 的允许值范围。恰恰相反——通过为 Name 包含一个受保护的设置器,FooBase 正在创建一个期望,即该值可以通过派生类的内部机制进行更改。

于 2012-07-13T15:06:02.153 回答
0

LSP 违反本质上是违反,如果
1. 超类型的不变量被子类型违反 OR
2. 超类型的先决条件被子类型加强 OR
3. Post 条件被 Sub 类型削弱。

上述三个条件必须在界面设计时通过正式的“契约式设计”预先确定。
在您的示例中, FooBase 没有定义任何扩展类要遵守的任何正式规则。
此外,名称设置器具有受保护的访问权限。因此,我不认为 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

于 2013-12-16T13:16:57.813 回答