5. 依赖倒转原则
约 1317 字大约 4 分钟
2026-02-27
依赖倒转原则
定义
依赖倒转原则(Dependence Inversion Principle, DIP):抽象不应该依赖细节,细节应该依赖于抽象。要针对接口编程,不要对实现编程。
依赖倒转原则
A.高层模块不应该依赖低层模块。两个都应该依赖抽象。
B.抽象不应该依赖细节。细节应该依赖抽象。
场景设计
UML类图举例
我们设计三个类,观察结果
- ILight类 ,灯光类——抽象类(稳定)。
- Light类,电灯类——底层模块(变化)。
- Switch类,开关类——高层模块(稳定)。
代码演示
//1. 抽象不应该依赖于实现细节,而是实现细节应该依赖于抽象。
// 抽象接口(稳定的部分):ILight
class ILight {
public:
virtual void turnOn() = 0;
virtual void turnOff() = 0;
};
// 低层模块(变化的部分):电灯
class Light :public ILight
{
public:
void turnOn() {
std::cout << "Light is on" << std::endl;
}
void turnOff() {
std::cout << "Light is off" << std::endl;
}
};
// 高层模块(稳定的部分):开关
//2. 高层模块不应该依赖于低层模块,它们都应该依赖于抽象。
// Switch 类不直接依赖于 Light 类,而是通过依赖于 ILight 接口来控制电灯的开关。
class Switch
{
public:
// 依赖于抽象接口,而不是具体的 Light 类,后续有任何新增的操作直接设计新类
// 继承ILight即可。
void control(ILight& light) {
if (m_isOn) {
light.turnOn();
m_isOn = false;
} else {
light.turnOff();
m_isOn = true;
}
}
private:
bool m_isOn = false;
};里氏替换原则
定义
里氏替换原则(Liskov Substitution Principle, LSP):一个软件实体如果使用的是一个父类的话,那么一定适用于其子类,而且它察觉不出父类对象和子类对象的区别。也就是说,在软件里面,把父类都替换成它的子类,程序的行为没有变化,简单地说,子类型必须能够替换掉它们的父类型。
- 子类必须能够替换它们的基类(IS-A)。
- 子类必须保持和父类接口的一致性
- 子类可以扩展父类的功能
- 子类不应该引入新的错误或异常(删除)
通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。【尽量不用重写父类的方法】
场景设计
UML类图举例
鸟一般都会飞行,如燕子的飞行速度大概是每小时 120 千米。但是新西兰的几维鸟由于翅膀退化无法飞行。假如要设计一个实例,计算这两种鸟飞行 300 千米要花费的时间。显然,拿燕子来测试这段代码,结果正确,能计算出所需要的时间;但拿几维鸟来测试,它的飞行速度是0,结果会发生“除零异常”或是“无穷大”,明显不符合预期.
//设计鸟类父类
class Bird
{
public:
//设置速度
void setFlySpeed(double speed)
{
m_birdSpeed = speed;
}
//得到时间, 距离/速度
double getFlyTime(double dis)
{
return (dis/bird_speed);
}
public:
double m_flySpeed;
};
//子类1 : 燕子类
class Swallow : public Bird
{
;
}
//子类2 : 几维鸟类
class BrownKiwi: public Bird
{
public:
void setFlySpeed(double speed)
{
bird_speed=0;
}
};
//测试代码: 使用智能指针管理堆区内存
std::unique_ptr<Bird> bird1 = std::make_unique<Swallow>();
std::unique_ptr<Bird> bird2 = std::make_unique<BrownKiwi>();
bird1->setFlySpeed(100);
bird2->setFlySpeed(100); //设置飞行速度是无效的,因为本质几维鸟飞行速度为0
double t1 = bird1->getFlyTime(300);
double t2 = bird2->getFlyTime(300); //此时会提示异常。实际写代码应该用try..catch异常处理问题分析
- 几维鸟类重写了鸟类的 setSpeed(double speed) 方法,这违背了里氏替换原则。
- 燕子和几维鸟都是鸟类,但是父类抽取的共性有问题,几维鸟的的飞行不是正常鸟类的功能,需要特殊处理,应该抽取更加共性的功能。
- 解决方法:取消几维鸟原来的继承关系,定义鸟和几维鸟的更一般的父类,如动物类,它们都有奔跑的能力。几维鸟的飞行速度虽然为 0,但奔跑速度不为 0,可以计算出其奔跑 300 千米所要花费的时间。
代码演示
class Animal
{
public:
//设置运动的速度
void setRunSpeed(double speed)
{
this->m_speed = speed;
}
//得到运动的时间
double getRunTime(double dis)
{
return dis/_speed;
}
private:
double m_speed;
};
class Bird : public Animal
{
public:
//设置速度
void setFlySpeed(double speed)
{
m_birdSpeed = speed;
}
//得到时间, 距离/速度
double getFlyTime(double dis)
{
return (dis/bird_speed);
}
public:
double m_birdSpeed;
};
//燕子继承鸟类 飞行属于燕子的特性
class Swallow : public Bird
{
;
}
//几维鸟继承动物类
class BrownKiwi : public Animal
{
;
}
//测试代码: 使用智能指针管理堆区内存
std::unique_ptr<Bird> bird1 = std::make_unique<Swallow>();
std::unique_ptr<Bird> bird2 = std::make_unique<BrownKiwi>();
bird1->setFlySpeed(100);
bird2->setRunSpeed(100); //设置奔跑的速度是有效的
double t1 = bird1->getTime(300);
double t2 = bird2->getTime(300);