首页 > 其他分享 >里氏替换原则经典反例:正方形不是长方形

里氏替换原则经典反例:正方形不是长方形

时间:2024-06-02 23:28:20浏览次数:29  
标签:Square width int 里氏 反例 height 正方形 Shape Rectangle

里氏替换原则指出:“继承必须确保超类所拥有的性质在子类中仍然成立”,在程序中的表现就是某个接口能接受超类对象为参数,那么它也必须应该能接受子类对象为参数,且程序不会出现异常。也就是说子类对象应该能够替换掉超类对象,而程序的行为不会改变。

最经典的用于说明里氏替换原则的反例就是“正方形不是长方形”。

假设我们有一个 Rectangle 类,它有 width 和 height 两个属性,以及它们的 getter 和 setter 方法,还有一个 area 方法用于求矩形的面积。

class Rectangle {
public:
    virtual void setWidth(int w) {
        width = w;
    }
    virtual void setHeight(int h) {
        height = h;
    }
    int getWidth() const {
        return width;
    }
    int getHeight() const {
        return height;
    }
    virtual int area() const {
        return width * height;
    }
protected:
    int width;
    int height;
};

这里的 width、height 需要设置为 protected,否则继承后将无法访问这两个属性。

然后我们创建一个 Square 类,它继承了 Rectangle 类,因为它们的长和高总是相等的,所以我们要重写 width 和 height 的 setter方法。

class Square : public Rectangle {
public:
    void setWidth(int w) override {
        width = height = w;
    }
    void setHeight(int h) override {
        width = height = h;
    }
};

然后现在有个接口,它接受 Rectangle 对象的引用作为参数,并设置长和宽,然后调用 area 并设置断言判断与预期是否一致。

void process(Rectangle& r) {
    r.setWidth(5);
    r.setHeight(4);
    assert(r.area() == 20); // 当 r 为 Square 时断言错误。
}

这里的断言在 r 为 Rectangle 时会成功,而 r 为 Square 时会失败。

int main() {
    Rectangle r;
    process(r); // 成功

    Square s;
    process(s); // 失败

    return 0;
}

很明显,在接口 process 中 Square 不能替换 Rectangle,因为当 Square 替换 Rectangle 作为参数时,程序发生了异常,出现了预期之外的结果。而最根本的原因是 Square 不能继承 Rectangle 中,因为 Rectangle 的属性 width 和 height 并不全是 Square 应该拥有的属性,或者说 Square 不应该拥有两个独立的属性,而应该拥有单一的边长属性 side。

所以,为了确保里氏替换原则成立,我们应该取消 Square 对 Rectangle的继承,重新给 Square 和 Rectangle 设计一个更高层的抽象,如 Shape,Shape 中有一个 Square 和 Rectangle 共有的属性 area,然后让 Square 和 Rectangle 都继承 Shape。

抽象类 Shape:

class Shape {
public:
    virtual int area() const = 0; 
};

Rectangle 继承 Shape 重写 area 接口并定义自己独特的成员变量 width 和 height 以及对应的 setter:

class Rectangle : public Shape {
public:
    void setWidth(int w) {
        width = w;
    }
    void setHeight(int h) {
        height = h;
    }
    int area() const override {
        return width * height;
    }
private:
    int width;
    int height;
};

Square 继承 Shape 重写 area 接口并定义自己独特的成员变量 side 以及对应的 setter:

class Square : public Shape {
public:
    void setSide(int s) {
        side = s;
    }
    int area() const override {
        return side * side;
    }
private:
    int side;
};

接口 process 接收超类 Shape 作为参数:

void process(Shape& s) {
    std::cout << "Area: " << s.area() << std::endl;
}

在 main 函数中 process 分别接受 Rectangle 和 Square 类型对象:

int main() {
    Rectangle r;
    r.setWidth(5);
    r.setHeight(4);
    process(r); // 成功

    Square s;
    s.setSide(5);
    process(s); // 成功

    return 0;
}

运行程序后发现,无论是 Rectangle 还是 Square 类型对象 process 接口均能正常处理并且没有出现异常,也就意味着 Rectangle 和 Square 两个子类能替换掉超类并且程序行为没有改变,也就说明这次的继承关系和接口设计符合里氏替换原则。

以上就是本文的全部内容,需要完整可运行代码请查看 Github 仓库GnCDesignPatterns

标签:Square,width,int,里氏,反例,height,正方形,Shape,Rectangle
From: https://blog.csdn.net/a2025834646/article/details/139397691

相关文章

  • 牛客网刷题 | BC111 空心正方形图案
    目前主要分为三个专栏,后续还会添加:    专栏如下:          C语言刷题解析    C语言系列文章    我的成长经历感谢阅读!初来乍到,如有错误请指出,感谢!描述KiKi学习了循环,BoBo老师给他出了一系列打印图案的练习,该任务是打印用“*”组......
  • 最大正方形
    题目描述在一个$n\timesm$的只包含$0$和$1$的矩阵里找出一个不包含$0$的最大正方形,输出边长。输入格式输入文件第一行为两个整数$n,m(1\leqn,m\leq100)$,接下来$n$行,每行$m$个数字,用空格隔开,$0$或$1$。输出格式一个整数,最大正方形的边长。样例输入4401......
  • 软件设计原则—里氏代换原则
    里氏代换原则是面向对象设计的基本原则之一。里氏代换原则:任何基类可以出现的地方,子类一定可以出现。通俗理解:子类可以扩展父类的功能,但不能改变父类原有的功能。换句话说,子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。如果通过重写父类的方法来完成新的......
  • 只用CSS实现一个自适应的正方形
    问题描述当一个div的宽度是自适应的,高度需要适配宽度的变化一起变化时,CSS的样式设置好像变得不那么容易或许大多数人都会选择js实现,没问题,性能低一点罢了但实际上纯css也能实现代码//html<divclass="container"><divclass="father"><divclass="son">test</div......
  • 2024-04-10:用go语言,考虑一个非负整数数组 A, 如果数组中相邻元素之和为完全平方数,我们
    2024-04-10:用go语言,考虑一个非负整数数组A,如果数组中相邻元素之和为完全平方数,我们称这个数组是正方形数组。现在要计算A的正方形排列的数量。两个排列A1和A2被认为是不同的,如果存在至少一个索引i,满足A1[i]!=A2[i]。输入:[1,17,8]。输出:2。答案2024-04-10:来自左......
  • lc996 正方形数组的数目
    给定非负整数数组A[n],返回A的不同排列数目,使用数组每对相邻元素之和是一个完全平方数。1<=n<=12;0<=A[i]<=1e9状压dp,记dp[st][i]表示已选择数的状态为st,并且最后选择数的下标为i的方案数,对于某个状态st,枚举最后选择的数i是哪个,以及上一个最后选择的数j是哪个,进行转换。由于A可......
  • 【C语言】空心正方形图案
    思路:1,两行两列打印*:第一行和最后一行,第一列和最后一列。2,其他地方打印空格。代码如下:#include<stdio.h>intmain(){  intn=0;  inti=0;  intj=0;  while(scanf("%d",&n)!=EOF)    for(i=0;i<n;i++)    {......
  • 1056:点和正方形的关系
    【题目描述】有一个正方形,四个角的坐标(x,y)分别是(1,-1),(1,1),(-1,-1),(-1,1),x是横轴,y是纵轴。写一个程序,判断一个给定的点是否在这个正方形内(包括正方形边界)。如果点在正方形内,则输出yes,否则输出no。【输入】输入一行,包括两个整数x、y,以一个空格分开,表示坐标(x,y)。【输出】......
  • 面向对象设计的六大原则(SOLID原则)-——里氏替换原则
    里氏替换原则(LiskovSubstitutionPrinciple,LSP)是面向对象设计的基本原则之一,由BarbaraLiskov提出。它表明,如果程序中的对象使用的是基类型的话,那么无论它实际上使用的是哪一个子类的对象,程序的行为都不会发生改变。简单来说,子类型必须能够替换它们的基类型,而且替换后程序的行......
  • 单独补题-数正方形
    数正方形题意:做法:发现边长为1的正方形,中间不能放正方形。边长为2的正方形中间可以放1个正方形...以此类推。又容易计算出边长为x的正方形在n*n的矩阵中有几个。constintmod=1e9+7;voidsolve(){//JP8692[蓝桥杯2019国C]数正方形--思维..intn,ans=0;......