Liskov替代原则(LSP)与代码示例

问题描述 投票:0回答:2

利斯科夫替代原则要求

  1. 子类型不能强化前提条件。
  2. 后置条件不能在子类型中被削弱。
  3. 超类型的不变量必须保留在子类型中。
  4. 历史约束(“历史规则”)。对象被认为只能通过他们的方法(封装)进行修改。由于子类型可能引入超类型中不存在的方法,因此引入这些方法可能允许子类型中的状态更改,这些更改在超类型中是不允许的。历史约束禁止这一点。

任何人都可以发一个违反这些要点的例子和另一个解决这些问题的例子吗?

solid-principles liskov-substitution-principle
2个回答
1
投票

你知道ICollection接口吗?想象一下,你正在编写一个方法来获取ICollection并使用其Add方法或更好的方法来操作它的Clear方法如果有人传递ReadOnlyCollection(实现ICollection),你将获得使用Add的异常。现在你永远不会想到,因为接口定义了,所以ReadOnlyCollection违反了LSP。


0
投票

问题中的所有四个项目都在this article进行了彻底的审查。

子类型不能强化前提条件。

This answer呈现“真正的鸭子”和“电子鸭”的例子,我建议你去看看吧。为简洁起见,我会在这个项目中使用它。

这意味着子类型不会妨碍原始方法在基类中的行为方式。在上面提到的答案的代码中,两只鸭子都可以游泳,但ElectricDuck只有在它打开时才会游泳。因此,任何需要鸭子(来自界面IDuck)现在游泳的代码单元将无法工作,除非明确指出鸭子是ElectricDuck(然后打开),这需要在任何地方实现。

后置条件不能在子类型中被削弱。

对于这个,我们可以退出鸭子类比。我们以this答案为基础。假设我们有一个只接受正整数的基类。如果在子类型中,在扩展方法时,我们删除了数字必须为正数的条件,那么过去认为数字为正的所有代码单元现在都有破坏的风险,因为现在不能保证这个数字是积极的。这是这个想法的代表:

public class IndexBaseClass
{
    protected int index;
    public virtual int Index
    {
        get
        {
            //Will return positive integers only
            return index < 0 ? 0 : index;
        }
        set
        {
            index = value;
        }
    }
}

public class IndexSubClass : IndexBaseClass
{
    public override int Index
    {
        get
        {
            //Will pay no mind whether the number is positive or negative
            return index;
        }
    }
}

public class Testing
{
    public static int GetIndexOfList(IndexBaseClass indexObject)
    {
        var list = new List<int>
        {
            1, 2, 3, 4
        };

        return list[indexObject.Index];
    }
}

如果我们调用GetIndexOfList传递IndexSubClass对象,则无法保证该数字是正数,因此可能会破坏应用程序。想象一下,你已经从你的代码中调用了这个方法。在所有实施中,您都必须浪费时间检查正值。

超类型的不变量必须保留在子类型中。

父类可能有一些不变量,也就是说,只要对象存在,一些条件必须保持为真。在所有实现到目前为止的风险下,没有子类应继承该类并消除此不变量。在下面的例子中,父类抛出一个Exception,如果它是负数然后设置它,但子类只是忽略它,它只是设置不变量。

以下代码取自here

public class ShippingStrategy
{
    public ShippingStrategy(decimal flatRate)
    {
        if (flatRate <= decimal.Zero)
            throw new ArgumentOutOfRangeException("flatRate", "Flat rate must be positive 
  and non-zero");

        this.flatRate = flatRate;
    }

    protected decimal flatRate;
}

public class WorldWideShippingStrategy : ShippingStrategy
{
    public WorldWideShippingStrategy(decimal flatRate)
        : base(flatRate)
    {
        //The subclass inherits the parent's constructor, but neglects the invariant (the value must be positive)
    }

    public decimal FlatRate
    {
        get
        {
            return flatRate;
        }
        set
        {
            flatRate = value;
        }
    }
}

历史约束(“历史规则”)。

这个与最后一条规则相同。它声明子类型不应该引入在父类中改变不可变属性的方法,例如在子类中将新的Set方法添加到曾经只能通过构造函数设置的属性。

一个例子:

public class Parent
{
    protected int a;

    public Parent(int a)
    {
        this.a = a;
    }
}

public class Child : Parent
{
    public Child(int a) : base(a)
    {
        this.a = a;
    }

    public void SetA(int a)
    {
        this.a = a;
    }
}

现在,由于子类,父类中的先前不可变属性现在是可变的。这也违反了LSP。

© www.soinside.com 2019 - 2024. All rights reserved.