基本格
;违背设计原则而写出的程序,会使程序的质量属性[可扩展,可维护,可测试,可重用,…]急剧下降。理解设计原则,并会使用设计模式的程序员是成为中高级工程师
的必要条件。高内聚低耦合
设计思想、面向过程、面向对象、函数式编程思想。solid
,因此后来就被被称为SOLID
原则,SOLID
原则起初主要是在OOP中针对接口设计的指导原则,其实这些原则还可以作为函数、类以及模块的设计原则。遵循这几个原则可以编写出扩展性好,可维护性好,透明性好,可读性好的软件系统。SOLID
设计原则还是程序设计方面的具体原则。在软件工程中针对的level
或phase
大概是处于架构设计和编码实现的中间阶段 ------- 程序详细设计阶段。SOLID
最后一个,但其实,DIP是非常基础和重要的设计原则。其余四个原则几乎都或多或少有DIP的影子,比如违反了DIP,也大概率会违反开闭原则(OCP)。我们通过对一个具体需求的分析、设计和实现来说明如何遵循依赖倒置原则。违背和不违背DIP原则分别对扩展性有什么影响。比如有一个业务需求:
我期待通过一个二值自锁开关类来控制一个两状态的台灯。按下开关,台灯打开;抬起开关,台灯关闭。
class Switch
和class Lamp
两个类。#include "lamp.hpp"
#include "switch.hpp"
// wrapper 类,组装两个功能类对象
class Client {
public:
Client() {
// 创建一个位于卧室的台灯对象
lamp_ = Lamp("bed room");
// 将创建到好的台灯对象交给Swich对象来管理,继而实现控制
switch_ = Switch(lamp_); // 注意这里用的是引用或指针而不是值拷贝
}
// 开灯
bool openLamp() { return switch_.open(); }
// 关灯
bool closeLamp() { return switch_.close(); }
private:
Lamp lamp_;
Switch switch_;
};
// Application main entry point
void main() {
Client client;
client.openLamp();
client.closeLamp();
}
class Switch
和class Lamp
两个类实现。Lamp
// file lamp.hpp
class Lamp {
public:
Lamp(const std::string &location) : location_(location) {}
public:
bool open() {
bool flag = false;
// 具体实现细节
// ...
return flag;
}
bool close() {
bool flag = false;
// 具体实现细节
// ...
return flag;
}
private:
std::string location_;
};
Switch
// file switch.
#include
class Switch {
public:
// 构造域
Switch(Lamp *lamp) : lamp_(lamp) {}
Switch(Lamp &lamp) : lamp_(&lamp) {}
// 接口域
public:
bool open() { return lamp_->open(); }
bool close() { return lamp_->close(); }
private:
Lamp *lamp_;
};
Switch
和类Lamp
。类Lamp
聚合(Aggregation)到类Switch
。类Swith
是高层模块,类Lamp
是低层模块。依赖关系为类Switch
到Lamp
,任何导致Lamp
变化的原因都会导致Switch
的变动,这种依赖关系是静态的,编译时的依赖。大部分情况下高层模块Swith
的代码相对稳定,只具有open
和close
两种操作,而这两种操作都是转调用底层模块的具体实现{open(); close()}
。而低层模块Lamp
相对不稳定,目前的依赖关系违背了依赖导致原则,高层模块Switch
直接依赖的低层模块,导致高层Switch
不易变化的模块的代码和低层Lamp
容易变化的代码静态绑定到一起,继而导致高层模块Switch
的代码不可复用。为了解决Switch
可复用性问题,我们引入解决方案二。Switch
代码,我们将引入一个抽象的中间类(引入一个中间层[分层]是计算机领域解决问题的好方法),名称为IControlable
来抽象Switch
可以操作的所有对象,包括Lamp
对象,甚至未来的Motor
、Vehicle
等所有具有二元[0-1]操作的可控制对象。// file icontrolable.hpp
// 抽象类 或 接口类
class IControlable {
public:
virtual ~IControlable() {}
public:
virtual bool open() = 0;
virtual bool close() = 0;
};
Switch
依赖接口类IControlable
,让类Lamp
实现接口类IControlable
,解耦Switch
和Lamp
的直接依赖关系,解除Switch
对象只能控制Lamp
对象的尴尬局面(--)。此时类图如下Switch
依赖接口类IControlable
;同时Lamp
也依赖接口类IControlable
。Switch
不在依赖Lamp
了,这就是DIP原则的两种定义所描述的完整内涵----高层模块不应该依赖低层模块,二者都应该依赖于抽象;抽象不应该依赖实现细节,实现细节应该依赖于抽象。这里Switch
是高层模块,Lamp
是低层模块也是实现细节,而IControlable
是抽象。至此,该设计完全遵循DIP原则了。Switch
文件switch.hpp
也只依赖IControlable.hpp
,不会有Lamp
之lamp.hpp
的影子。一般来说控制器Switch
的能力由IControlable
来描述和限制,我们会把IControlable
也看做高层模块的一部分,即IControlable
属于高层模块。类图如下右边所示:
依赖关系由之前的 高层Switch
模块直接依赖低层和实现细节模块Lamp
, 进化为 低层Lamp
模块依赖了高层Switch
模块,由之前的稳定依赖于不稳定进化为不稳定依赖于稳定。这就是依赖倒置中倒置的深刻含义。
SOLID
原则 ----依赖倒置原则(DIP
)的含义,并从复用性和可扩展性方面给出了一个例子,希望读者能认真思考,举一反三,将设计原则转化为自己的编程内功,修炼和提高自己的编程素养。