当前位置: 首页 > 知识库问答 >
问题:

理解Liskov替代原理

郭俊拔
2023-03-14

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

撇开仍然让我悲伤的协变和逆变的概念不谈,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();
    }
}

所以我的问题是:基于上述两个概念,我用这个例子是否违反了原则?若否,原因为何?

事先非常感谢。

共有3个答案

洪浩
2023-03-14

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

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

巫马令
2023-03-14

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

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

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

谭高峯
2023-03-14

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

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

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

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

 类似资料:
  • 来自维基百科, Liskov的行为子类型概念定义了对象的可替代性概念;也就是说,如果S是T的子类型,则程序中T类型的对象可以替换为S类型的对象,而不改变该程序的任何期望属性(例如正确性)。 假设以下类层次结构: 基本抽象类-。它有一个只读属性,在后继程序中被重写。 基类的继承者-,它重写并返回灰色。 Cat的继任者-,它覆盖并返回带条纹的。 然后我们声明一个方法,参数类型为(不是)。 向该方法发送

  • 假设我有一个抽象类鸟,它的一个函数是飞(int高度)。 我有许多不同的鸟类,每个类都有自己不同的飞的实现,这个函数在整个应用程序中被广泛使用。 有一天,我的老板来了,要求我添加一只鸭子,它做其他鸟类所做的一切,只是它不飞,而是在应用程序的池塘里游泳。 将duck添加为bird的子类型违反了Liskov替换规则,因为在调用duck时。我们要么抛出异常,要么什么也不做,要么违反正确性原则。 在牢记坚实

  • 我在理解这两个原则时有些困难。这是一个有点长的阅读问题,所以要有耐心。 假设我们有一个类 和接口 然后我们创建了两个子类 现在我们将使implements 并在儿童课程中做出改变 并为该结构创建测试函数(如下LSP): 在这里我想停下来,因为实际上在下一步我被绊倒了。如果我们要创建第三个类? 圆没有边,所以为这个孩子实现听起来很可笑。好的,我们可以只将实现移到四边形和三角形,但在这种情况下LSP将

  • 我试图理解Liskov替换原理,我有以下代码: 我不确定这是否违反了它。原理是,如果你有一个类S的对象,那么你可以用另一个类T的对象来代替它,其中S是T的一个子类。但是,如果我写了 这当然会产生编译错误,因为Vehicle类没有openDoor()方法。但这意味着我不能用它们的父类Vehicle替换VehicleWithDoors对象,这似乎违反了原则。那么这个代码是否违反了它?我需要一个好的解释

  • LSP定义指出,如果S是T的子类型,则程序中T类型的对象可以替换为S类型的对象,而不改变该程序的任何期望属性。 子类型中的前提条件不能加强 例如,我有下面的类,这是违反(在子类型中不能加强前提条件)。我正试图把我的头绕在这上面,请有人提供一个好的例子来理解它。

  • Liskov替代原则(LSP)和界面分离原则(ISP)之间有什么核心区别吗?最终,这两种方法都是为了设计具有通用功能的界面,并在您具有特殊功能时引入新的界面。