基于多态的依赖倒置原则 DIP (Dependency Inversion Principle)
在设计一个系统的过程中,存在这几个模块:
(1)高层模块(负责复杂的业务逻辑) (2)底层模块(负责具体的实现操作)
传统的过程化设计:自顶向下,逐层分解。使高层次的模块依赖于低层次的模块。
如果让高层模块直接依赖底层模块,那么如果高层需要变动,则低层也要跟着全部修改,将会带来不必要的风险。
在这种背景下,我们来看依赖倒置原则的设计:
高层模块不会直接依赖于底层模块,二是在高层和底层之间建立一个中间抽象层。抽象层设置虚函数作为高底层的接口。使得高层模块依赖于抽象层得接口,底层来实现各个接口。使得高底层间接地发生联系,修改的话在抽象层即可完成。降低了模块之间的耦合性!(高内聚,低耦合)
依赖倒置原则基于这样一个事实: 相对于细节的多变性, 抽象的东西要稳定的多。以抽象为基础搭建起来的架构比以细节为基础搭建起来的架构要稳定的多。 在 C++中,抽象指的是抽象类(C++中称为接口), 细节就是具体的实现类,使用接口或者抽象类的目的是制定好规范和契约, 而不去涉及任何具体的操作, 把展现细节的任务交给他们的实现类去完成。
依赖倒置原则的核心思想是:面向接口编程。下边细细去体会~
下面用一个例子来直观的看下。
顶层:mama讲故事。
底层:讲些什么呢?有故事书,有报纸上的段子。
1、用传统的过程化来设计
(1)Book 、NewsPaper 的内容设计
(2)Mother 阅读模块,该模块需要写入Book、NewsPaper 各个底层模块。 顶层直接依赖于底层模块。
class Mother
{
public:
//讲故事书 Book
void tellStory(Book *b)
{
cout<<b->getContents()<<endl;
}
//讲报纸
void tellStory(NewsPaper *p)
{
cout<<p->getContents()<<endl;
}
};
每次换一种读物时,读Mother模块就要添加一种读物。那如果内容较多,Book、NewsPaper 再有 鬼故事、污段子……就需要不停的修改Mother模块,耦合性太高了。
2、DIP设计
我们添加一个抽象层:IReader 抽象层——读物。
//中层 接口层
class IReader
{
public:
virtual string getContents() = 0;
};
//Mother 为顶层业务逻辑
class Mother
{
public:
void tellStory(IReader *i)
{
cout<<i->getContents()<<endl;
}
};
Mother 类与接口 IReader 发生依赖关系,而 Book 和 Newspaper 都属于读物的范畴,他们各自都去实现 IReader 接口,这样就符合依赖倒置原则了。中间给出了IReader 抽象层接口,然后各种读物只需按照接口去实现各自虚函数就好了。
整个系统架构如下图所示:
这样修改后, 无论以后怎样扩展读物种类, 都不需要再修改 Mother 类了。 这只是一个简单的例子,实际情况中,代表高层模块的Mother 类将负责完成主要的业务逻辑,一旦需要对它进行修改,引入错误的风险极大。 所以遵循依赖倒置原则可以降低类之间的耦合性, 提高系统的稳定性, 降低修改程序造成的风险。
最后,再来看DIP的定义:
高层模块不应该依赖低层模块, 二者都应该依赖其抽象;
抽象不应该依赖细节, 细节应该依赖抽象。
采用依赖倒置原则给多人并行开发带来了极大的便利, 比如上例中, 原本 Mother类与 Book 类直接耦合时, Mother 类必须等 Book 类编码完成后才可以进行编码, 因为Mother 类依赖于 Book 类。 修改后的程序则可以同时开工, 互不影响, 因为 Mother与 Book 类没有直接关系。 参与协作开发的人越多、 项目越庞大, 采用依赖导致原则的意义就越重大。
实现代码:
#include <iostream>
using namespace std;
//中层 接口层
class IReader
{
public:
virtual string getContents() = 0;
};
//抽象不依赖细节。细节依赖于抽象
class NewsPaper:public IReader
{
public:
string getContents()
{
return "Read Newspapers";
}
};
class Book :public IReader
{
public:
string getContents()
{
return "Read Book";
}
};
//Mother 为顶层业务逻辑
//无论底层如何变动,我稳如老狗!XXX 稳如泰山!
class Mother
{
public:
void tellStory(IReader *i)
{
cout<<i->getContents()<<endl;
}
};
int main()
{
Mother m;
Book b;
NewsPaper p;
cout<<"start"<<endl;
//这才意识到多态的好处啊
m.tellStory(&b);
m.tellStory(&p);
return 0;
}
实践二:组装电脑
模块分解如图:
#include <iostream>
using namespace std;
//抽象层 接口层 制定规则
class Cpu
{
public:
virtual void run() = 0;
};
class Mem
{
public:
virtual void run() = 0;
};
class Disk
{
public:
virtual void run() = 0;
};
//底层厂商,按照规则生产(实现抽象的run虚函数)
class IntelCpu:public Cpu
{
public:
virtual void run ()
{
cout<<"i am IntelCpu"<<endl;
}
};
class KingStonMem:public Mem
{
public:
virtual void run ()
{
cout<<"i am KingStonMem"<<endl;
}
};
class WdDisk:public Disk
{
public:
virtual void run ()
{
cout<<"i am WdDisk"<<endl;
}
};
//顶层业务逻辑
class Computer
{
public:
Computer(Cpu *pc,Mem *pm,Disk *pd)
:_pc(pc),_pm(pm),_pd(pd){}
~Computer()
{
delete _pc;
delete _pm;
delete _pd;
}
void work ()
{
_pc->run();
_pm->run();
_pd->run();
}
private:
Cpu *_pc;
Mem *_pm;
Disk *_pd;
};
int main()
{
Computer c(new IntelCpu,new KingStonMem,new WdDisk);
c.work();
return 0;
}