SOLID原则学习,里氏替换原则

文章目录

  • [1. 定义](#1. 定义)
  • [2. 里氏替换原则的核心思想](#2. 里氏替换原则的核心思想)
  • [3. 违反里氏替换原则的后果](#3. 违反里氏替换原则的后果)
  • [4. 实现里氏替换原则的方法](#4. 实现里氏替换原则的方法)
  • [4. 实例分析](#4. 实例分析)
  • [5. 总结](#5. 总结)

1. 定义

里氏替换原则(Liskov Substitution Principle, LSP) 是面向对象设计中的五大原则(SOLID)之一,由 Barbara Liskov 提出。它是继承关系的核心原则,确保子类可以替换父类而不影响程序的正确性。

子类对象必须能够替换父类对象,且替换后程序的行为不会发生变化。

换句话说,如果一个程序使用了一个父类的对象,那么它应该能够使用该父类的任何子类对象,而不会产生错误或意外行为。


2. 里氏替换原则的核心思想

1. 子类必须完全实现父类的行为:

  • 子类必须实现父类的所有方法,且不能改变父类的行为。

  • 子类可以扩展父类的功能,但不能修改父类的原有功能。

2. 子类不能违背父类的约束:

  • 子类的方法参数范围必须与父类一致或更宽松。

  • 子类的方法返回值范围必须与父类一致或更严格。

3. 子类不能抛出父类未声明的异常:

  • 子类的方法不能抛出父类方法未声明的异常。

3. 违反里氏替换原则的后果

如果子类不能完全替换父类,可能会导致以下问题:

程序行为不一致。

难以维护和扩展。

引入隐藏的 bug。


4. 实现里氏替换原则的方法

1. 使用抽象基类或接口:

  • 定义清晰的接口或抽象类,确保子类实现所有必要的行为。

2. 避免子类修改父类的行为:

  • 子类只能扩展父类的功能,不能改变父类的核心逻辑。

3. 遵循契约设计:

  • 父类定义明确的契约(前置条件、后置条件、不变式),子类必须遵守这些契约。

4. 实例分析

场景描述

假设我们有一个表示矩形的类 Rectangle,它有一个计算面积的方法。现在我们需要支持正方形 Square,正方形是一种特殊的矩形。

违反里氏替换原则的实现

cpp 复制代码
class Rectangle {
protected:
    int width, height;

public:
    void setWidth(int w) { width = w; }
    void setHeight(int h) { height = h; }
    int getWidth() const { return width; }
    int getHeight() const { return height; }
    int calculateArea() const { return width * height; }
};

class Square : public Rectangle {
public:
    void setWidth(int w) {
        width = w;
        height = w; // 正方形需要保持宽高相等
    }

    void setHeight(int h) {
        width = h;
        height = h; // 正方形需要保持宽高相等
    }
};

void processRectangle(Rectangle& rect) {
    rect.setWidth(5);
    rect.setHeight(4);
    std::cout << "Expected area: 20, Actual area: " << rect.calculateArea() << std::endl;
}

int main() {
    Rectangle rect;
    processRectangle(rect); // 输出: Expected area: 20, Actual area: 20

    Square square;
    processRectangle(square); // 输出: Expected area: 20, Actual area: 16
    return 0;
}

问题分析:

  • Square 是 Rectangle 的子类,但它修改了 setWidth 和 setHeight 的行为,导致 processRectangle 函数的行为不一致。

  • 这违反了里氏替换原则,因为 Square 不能完全替换 Rectangle。


符合里氏替换原则的实现

为了避免违反里氏替换原则,我们可以重新设计类结构,将 Square 和 Rectangle 分离,或者使用组合代替继承。

cpp 复制代码
class Shape {
public:
    virtual int calculateArea() const = 0;
    virtual ~Shape() = default;
};

class Rectangle : public Shape {
private:
    int width, height;

public:
    Rectangle(int w, int h) : width(w), height(h) {}
    int calculateArea() const override {
        return width * height;
    }
};

class Square : public Shape {
private:
    int side;

public:
    Square(int s) : side(s) {}
    int calculateArea() const override {
        return side * side;
    }
};

void processShape(const Shape& shape) {
    std::cout << "Area: " << shape.calculateArea() << std::endl;
}

int main() {
    Rectangle rect(5, 4);
    processShape(rect); // 输出: Area: 20

    Square square(4);
    processShape(square); // 输出: Area: 16
    return 0;
}

改进点:

复制代码
Rectangle 和 Square 都继承自 Shape,但它们没有直接的继承关系。

processShape 函数可以处理任何 Shape 类型的对象,符合里氏替换原则。

5. 总结

里氏替换原则的核心是确保子类可以完全替换父类而不影响程序的正确性。通过合理设计类的继承关系,避免子类修改父类的行为,可以提高代码的可维护性和可扩展性。在实际开发中,如果发现子类无法完全替换父类,可能需要重新审视类的设计,考虑使用组合或其他设计模式来解决问题。

相关推荐
泽虞12 分钟前
《Qt应用开发》笔记
linux·开发语言·c++·笔记·qt
小邓儿◑.◑28 分钟前
贪心算法 | 每周8题(二)
c++·算法·贪心算法
闻缺陷则喜何志丹1 小时前
【剪枝 贪心 回溯】B4093 [CSP-X2021 山东] 发送快递|普及+
c++·算法·剪枝·贪心·洛谷
Maple_land2 小时前
Linux进程第八讲——进程状态全景解析(二):从阻塞到消亡的完整生命周期
linux·运维·服务器·c++·centos
ajassi20002 小时前
开源 C++ QT QML 开发(十一)通讯--TCP服务器端
c++·qt·开源
lyp90h2 小时前
高效SQLite操作:基于C++模板元编程的自动化封装
c++
minji...3 小时前
Linux相关工具vim/gcc/g++/gdb/cgdb的使用详解
linux·运维·服务器·c++·git·自动化·vim
_OP_CHEN3 小时前
C++基础:(九)string类的使用与模拟实现
开发语言·c++·stl·string·string类·c++容器·stl模拟实现
爱编程的化学家3 小时前
代码随想录算法训练营第27天 -- 动态规划1 || 509.斐波那契数列 / 70.爬楼梯 / 746.使用最小花费爬楼梯
数据结构·c++·算法·leetcode·动态规划·代码随想录
数字化顾问4 小时前
C++分布式语音识别服务实践——架构设计与关键技术
c++