理解合同和里氏替换原则

Understanding contracts and Liskov Substitution principle

考虑图表:

Collection - 抽象 class 与所有其他的公共部分:抽象函数 将整数放入集合并检查集合是否为空。

Bag - class 将集合实现为散列,您可以在其中轻松找到元素。 每个元素可能出现不止一次

我需要确定这种继承关系是否符合里氏代换原则。

所以,我首先检查函数的签名是否相同。 (put() 和 isEmpty())。 之后,我检查两者的方法合同是否相同,但我可以有合同抽象class吗?我不能 'create' 它。 如果是,就足以说明这两个 class 维护了 LSP? 还是需要其他东西?

简介

Liskov Substitution principle 是良好的面向对象设计 (AKA SOLID) 的 5 条基本原则之一:

Barbara Liskov 陈述的原则说 使用基 classes 引用指针的函数必须能够在不知情的情况下使用派生 classes 的对象。这是一个很好的设计原则,因为当一个函数不符合 LSP 时,它必须知道基 class.

所有可能的导数

当您考虑违反该原则的后果时,该原则的重要性就显而易见了。让我们举个例子。假设我们正在开发一个处理形状的程序。我们已经创建了 class Rectangle,并且在设计 class Square 时,我们认为很自然地继承自 class Rectangle:

使用 C++ 我们会写:

class Rectangle
{
public:
        virtual void SetWidth(const double w) {width_=w;}
        virtual void SetHeight(const double h) {height_=h;}
        virtual double GetHeight() const {return height_;}
        virtual double GetWidth() const {return width_;}
private:
        double width_;
        double height_;
};

class Square : public Rectangle
{
public:
        virtual void SetWidth(const double w) {
            Rectangle::SetWidth(w);
            Rectangle::SetHeight(w);
        }
        virtual void SetHeight(const double h) {
            Rectangle::SetWidth(w);
            Rectangle::SetHeight(h);
        }
};

void function (Rectangle& r)
{
        r.SetWidth(5);
        r.SetHeight(4);
        assert(r.GetWidth() * r.GetHeight()) == 20);
}

从示例中可以清楚地看出,断言(和 Liskov 项目)被违反了,因为矩形有一个 属性(身高和体重大小的独立性),它不坚持一个广场。

要回答您的问题,您应该推理适用于基础 class 但不适用于派生 class 的属性。如果界面设计得很好(看起来),你将找不到任何东西,你将能够用任何派生的 class.

替换基础 class

恕我直言,Square class 的问题是违反了 Rectangle 的合同:

  • 对于 RectangleWidthHeight 属性是独立的,因此如果您更改一个,另一个将保持原样
  • Square 通过更改每个 setter
  • 中的两个属性违反了此合同
  • 如果我们将 Square 传递给期望 Rectangle 的代码,并在 WidthHeight 不相关的前提下进行计数,那么我们就会遇到问题

我们还违反了历史规则(参见维基百科的文章Liskov Substitution Principle)。听起来很奇怪我将删除这些 class 之间的继承关系并引入一个 Shape 基础 class,因为那些拖车很难坚持 LS 原则。