C++设计模式:面向对象的八大设计原则之四

里氏替换原则(Liskov Substitution Principle,LSP)是面向对象设计中的一个重要原则,它指出子类必须能够替换它的基类,并且程序的行为不会发生改变。也就是说,在任何使用基类对象的地方,都可以透明地使用其子类对象。继承不仅是代码复用的手段,更重要的是表达了类型的抽象,子类是对基类的一种特殊化。

下面通过几个 C++ 代码示例来详细讲解里氏替换原则。

示例一:简单的继承与替换

首先,我们定义一个基类 Shape 表示形状,然后派生出子类 Rectangle(矩形)和 Square(正方形)。

cpp 复制代码
#include <iostream>



// 基类:形状

class Shape {

public:

    virtual double area() const = 0;

    virtual ~Shape() {}

};



// 子类:矩形

class Rectangle : public Shape {

private:

    double width;

    double height;



public:

    Rectangle(double w, double h) : width(w), height(h) {}

    double area() const override {

        return width * height;

    }

};



// 子类:正方形

class Square : public Shape {

private:

    double side;



public:

    Square(double s) : side(s) {}

    double area() const override {

        return side * side;

    }

};



// 计算形状面积的函数

void printArea(const Shape& shape) {

    std::cout << "Area: " << shape.area() << std::endl;

}



int main() {

    Rectangle rect(3, 4);

    Square square(5);



    // 可以用子类对象替换基类对象

    printArea(rect);

    printArea(square);



    return 0;

}    

代码解释

Shape 是一个抽象基类,定义了纯虚函数 area(),用于计算形状的面积。

Rectangle 和 Square 是 Shape 的子类,分别实现了 area() 函数。

printArea 函数接受一个 Shape 类型的引用作为参数,在 main 函数中,我们可以将 Rectangle 和 Square 对象传递给 printArea 函数,这体现了子类可以替换基类的特性。

示例二:违反里氏替换原则的情况

有时候,如果子类的行为与基类的预期行为不一致,就会违反里氏替换原则。例如,我们对上面的 Rectangle 和 Square 类进行修改,添加 setWidth 和 setHeight 方法。

cpp 复制代码
#include <iostream>



// 基类:矩形

class Rectangle {

protected:

    double width;

    double height;



public:

    Rectangle(double w, double h) : width(w), height(h) {}

    virtual void setWidth(double w) { width = w; }

    virtual void setHeight(double h) { height = h; }

    double area() const {

        return width * height;

    }

};



// 子类:正方形

class Square : public Rectangle {

public:

    Square(double s) : Rectangle(s, s) {}

    void setWidth(double w) override {

        width = w;

        height = w;

    }

    void setHeight(double h) override {

        width = h;

        height = h;

    }

};



// 调整矩形尺寸并打印面积的函数

void resizeAndPrint(Rectangle& rect) {

    rect.setWidth(5);

    rect.setHeight(10);

    std::cout << "Area: " << rect.area() << std::endl;

}



int main() {

    Rectangle rect(3, 4);

    Square square(5);



    // 对于矩形,预期结果是 5 * 10 = 50

    resizeAndPrint(rect);



    // 对于正方形,结果不符合预期,因为正方形的宽和高总是相等的

    resizeAndPrint(square);



    return 0;

}

代码解释

在这个例子中,Square 类继承自 Rectangle 类,但是 Square 类的 setWidth 和 setHeight 方法会同时修改宽和高,这与 Rectangle 类的预期行为不一致。

resizeAndPrint 函数期望传入的 Rectangle 对象可以独立地设置宽和高,但是当传入 Square 对象时,结果不符合预期,这就违反了里氏替换原则。

总结

里氏替换原则强调了继承关系中类型的兼容性和行为的一致性。在设计类和继承体系时,要确保子类能够完全替换基类,并且不会破坏程序的正确性。如果子类的行为与基类的预期行为不一致,就可能需要重新考虑类的设计,例如使用组合而不是继承。

相关推荐
hehelm2 分钟前
C++11 新特性
c++
初心未改HD3 分钟前
Go 泛型完全指南:从入门到实战
开发语言·golang
我不是懒洋洋5 分钟前
【数据结构】排序算法(直接插入排序、希尔排序、选择排序、堆排序、冒泡排序、快速排序、归并排序、计数排序)
c语言·数据结构·c++·经验分享·算法·排序算法
西红柿炒番茄317 分钟前
【Python】一个自动切换壁纸的python程序
开发语言·python
邪修king7 分钟前
UE5:C++ 实现 游戏逻辑 ↔ UI 双向联动
c++·游戏·ue5
ShiJiuD66688899910 分钟前
JSP Cookie和Session
java·开发语言
FQNmxDG4S9 小时前
Java多线程编程:Thread与Runnable的并发控制
java·开发语言
前端老石人9 小时前
HTML 字符引用完全指南
开发语言·前端·html
matlab_xiaowang9 小时前
Redux 入门:JavaScript 可预测状态管理库
开发语言·javascript·其他·ecmascript
虹科网络安全9 小时前
艾体宝干货|数据复制详解:类型、原理与适用场景
java·开发语言·数据库