Solid 原则:LSP 违规

Solid principle: LSP violation

我正在尝试以正确的方式学习 OOP 和 OOD 原则。我想澄清一下 Liskov 替换原则及其 PRE 和 POST 条件。我在这里阅读了一些主题,http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod 和其他地方的一些文章。

我已经写了一个简单的基础 class 和几个示例子 classes 以及我对它们中的很多的 pre 和 post 条件的假设,我想知道它们是否是正确的。 注释行是我的想法:它是否违反了 PRE 和 POST 条件。

public abstract class BaseClass
{
    public virtual int GetResult(int x, int y)
    {
        if (x > 10 && y < 20)
        {
            return y - x;
        }
        throw new Exception();
    }
}

public class LSPExample1 : BaseClass
{
    public override int GetResult(int x, int y)
    {
        // PRE: weakened pre condition is ok
        if (x > 10 && y <= 15)
        {
            // POST: Is it ok? because the available result range is narrowed by y <= 15 
            return y - x;
        }
        throw new Exception();
    }
}

public class LSPExample2 : BaseClass
{
    public override int GetResult(int x, int y)
    {
        // PRE: Same as base - OK
        if (x > 10 && y < 20)
        {
            // POST: I assume it's bad because of parameters place changed in substraction ?
            return x-y;
        }
        throw new Exception();
    }
}

public class LSPExample3 : BaseClass
{
    public override int GetResult(int x, int y)
    {
        // PRE Condition is bad (Strenghtened) because of (X >5) and (Y>20) ?
        if (x > 5 && y > 20)
        {
            // POST condition is ok because base class do substraction which is weaker than multiplication ?
            return x * y;
        }
        throw new Exception();
    }
}

非常感谢您的宝贵时间

这是您可以真正找到源头的美妙情况之一。 Barbara Liskov 的原始论文可用且易于访问且易于阅读。 http://csnell.net/computerscience/Liskov_subtypes.pdf

您显示的所有四个 GetResult 方法都接受两个整数作为输入和 return 一个整数,或者抛出异常。在这方面,他们都有相同的行为,所以可以说 LSP 是完全满意的。没有 explicit pre/postconditions 或不变量,关于代码真的没什么可说的。

您的示例中缺少的是合同。 GetResult 需要调用者做什么,保证会产生什么?

例如,如果合约保证 return 值等于 y - x,则示例 2 和 3 使合约失败,因此破坏了 LSP。但是,如果唯一的保证是 return 值将是 Int 或 Exception,那么它们都会通过。

如果合约保证并且如果 x <=10 将抛出异常 || y >= 20 然后示例 1 和 3 中断 LSP。如果唯一的保证是该方法将 return 一个 Int 或抛出一个 Exception,那么它们都满足它。

代码无法告诉您保证是什么,它只能告诉您希望代码做到。

因为我得到了很多赞成票,我将添加一个示例(伪代码):

class Line {
    int start
    int end
    int length() { return end - start } // ensure: length = end - start

    void updateLength(int value) {
        end = start + value
        // ensure: this.length == value
    }
}

这两个函数中的 "ensure" 子句是关于调用函数后 Line 对象状态的保证。只要我满足 sub-classes 中的保证,我的 sub-class 就会符合 LSP。例如:

class Example1: Line {
    void updateLength(int value) {
        start = end - value
    }
}

以上满足LSP

如果 Line 的 updateLength 函数也有 "this.start unchanged" 的 ensure 子句,那么我的 subclass 将不满足 LSP。