覆盖方法是违反LSP的唯一方法

时间:2018-12-18 12:21:49

标签: c# oop liskov-substitution-principle

我一直在寻找代码中的迹象,以表明可能会违反Liskov的替代原则。 首先,我做了一个简单的类,并继承了另一个类:

public class Adder
{
    public virtual int Add(int operand1, int operand2)
    {
        return operand1 + operand2;
    }
}

public class AdvancedAdder : Adder
{
}

然后我创建了一个UnitTest来检测LSP违规:

public abstract class AdderUnitTestsBase
{
    protected static Adder Adder;

    [DataTestMethod]
    [DataRow(2, 2, 4)]
    [DataRow(-1, 2, 1)]
    [DataRow(2, -3, -1)]
    [DataRow(0, 0, 0)]
    public void Add_ReturnsCorrectResult(
        int operand1, int operand2, int result)
    {
        Assert.AreEqual(result, Adder.Add(operand1, operand2));
    }
}


[TestClass]
public class AdderUnitTests : AdderUnitTestsBase
{
    [ClassInitialize]
    public static void ClassInit(TestContext context)
    {
        Adder = new Adder();
    }
}

[TestClass]
public class AdvancedAdderUnitTests : AdderUnitTestsBase
{
    [ClassInitialize]
    public static void ClassInit(TestContext context)
    {
        Adder = new AdvancedAdder();
    }
}

然后我尝试了不同的操作,例如更改参数类型或更改参数数量:

public class AdvancedAdder : Adder
{
    public int Add(int operand1, int operand2, int operand3)
    {
        return operand1 + operand2 + operand3;
    }

    public uint Add(uint operand1, uint operand2)
    {
        return operand1 + operand2;
    }
}

它没有违反LSP,因为方法具有不同的签名。它只是扩展功能。 唯一有效的方法是使用“替代”关键字:

public class AdvancedAdder : Adder
{
    public override int Add(int operand1, int operand2)
    {
        return operand1 - operand2;
    }
}

如果我避免在C#中使用“替代”,就不必担心可能会违反Liskov的“替代原则”。您认为这是正确的吗?

2 个答案:

答案 0 :(得分:4)

重载只是重新定义方法的技术解决方案。 LSP与语义有关,而不是技术。

  

如果我避免在C#中使用“替代”,则不必担心可能会违反Liskov的Substitution Principle。

也许您是说多态性,因为override只是改变对象行为(动态绑定)的一种方法。您也可以将new用于非virtual方法(静态绑定)。您甚至可以使用检查(作为反射的一种特殊情况)并根据此更改行为:

public void method()
{
    switch (this.getType().Name)
    {
        case "Square":
            // do the square thing
            break;
        case "Rectangle":
            // do the rectangle thing
            break;
    }
}

这样定义的方法可以很好地打破LSP,而无需继承或使用override / new关键字。

它确实比普通的语法更复杂,并且有关语义学(含义)的更多信息。

即使具有正方形/矩形的经典LSP示例也与意义有关。如果您认为正方形/矩形只是没有语义底物的单词,那么您就不会期望它们的行为。结果,您没有理由认为您可以替代另一种并且不能破坏LSP。

另一方面,继承是一个告诉您的代码结构:

PARENT
  ^
  |
CHILD
  

孩子可以代替父母,因为它继承了所有行为。

答案 1 :(得分:0)

还值得一提的是,不应将LSP视为在任何情况下都必须执行的严格规则。

例如,复合模式(https://en.wikipedia.org/wiki/Composite_pattern)可以为您提供一种以统一方式处理所有节点的机制(这很好)。但是,如果严格解释LSP,则会违反LSP。

与大多数面向对象的规则和设计模式一样,它只是一个指导,应该为您指明正确的方向,但可能会被其他要求所取代。最后,应该降低而不应该增加体系结构的总体复杂性。例如,在这里,复合模式可以让您以更统一的方式处理对象并提供更简单的算法。

您的问题本身。如果您还考虑将消息传递用于面向对象的设计,那么您还将拥有处理特定消息/事件的对象类别,这也是打破LSP的一种方式(而不是仅使用虚拟和替代)。

最后,您应该考虑真相总是在中间。