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

时间:2015-07-01 06:49:18

标签: solid-principles liskov-substitution-principle

利斯科夫替代原则要求

  1. 无法在子类型中加强先决条件。
  2. 后置条件不能在子类型中被削弱。
  3. 超类型的不变量必须保留在子类型中。
  4. 历史约束(“历史规则”)。对象被认为只能通过他们的方法(封装)进行修改。由于子类型可能引入超类型中不存在的方法,因此引入这些方法可能允许子类型中的状态更改,这些更改在超类型中是不允许的。历史限制禁止这样做。
  5. 任何人都可以发一个违反这些要点的例子和另一个解决这些问题的例子吗?

2 个答案:

答案 0 :(得分:1)

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

答案 1 :(得分: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对象,则不能保证该数字为正数,因此有可能破坏应用程序。想象一下,您已经在代码中调用了此方法。您将不得不浪费时间检查所有实现中的正值。

  

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

父类可能具有一些不变量,即某些条件只要对象存在就必须保持为真。到目前为止,所有子类都可能会崩溃,因此子类不能继承该类并消除该不变性。在下面的示例中,如果父类为负,则抛出异常,然后对其进行设置,但是子类只是将其忽略,而是对其进行设置。

以下代码摘自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;
        }
    }
}
  

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

这与最后一条规则相同。它指出,子类型不应在父类中引入 mutate immutable 属性的方法,例如在子类中向属性添加新的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。