转载: http://hi.baidu.com/blue_never_died/blog/item/5742eaf89e32500fd9f9fde1.html
DIP依赖倒置原则
DIP依赖倒置原则,是解决层间耦合的一个方法。假设有两个层,每个层各有一个类:A和B。A在上层,B在下层。A需要用到B的方法。
/*A.h*/
#include "B.h"
class A
{
public:
void f(B* b)
{
b->g();
}
};
我们经常会写这种代码。不是吗?
现在如果B.h文件做了修改,那么类A肯定要重新编译。最主要的是,类A直接使用了类B的方法,而类B是个具体类,假设现在有一个新的类C,它也同样实现了g(),我们想改成:
void f(C* b)
{
b->g();
}
可以看到,我们必须修改类A中函数f()的实现代码才能实现我们的目的。
但并不是什么代码都能轻易修改的。所以更好的解决办法就是利用DIP原则,让类A和类B共同依赖接口g()。只要接口g()不改变,那么我们就可以很容易的用不同的实现来代替,只要它们有相同的接口即可。
例如:
/*binterface.h*/
class binterface
{
public:
virtual void g()=0;
};
/*A.h*/
#include "binterface.h"
class A
{
public:
void f(binterface* b)
{
b->g();
}
};
现在A只依赖binterface了,只要类B和类C都实现了这个接口,那么我们就可以灵活的在类A中使用,同时不用修改类A的代码!
还有个问题,现在抽取出接口,那么这个接口从逻辑上讲到底是属于上层还是下层?(这可能会影响到文件的物理位置和包的接口)
根据专家的讲法,这个接口仍然属于下层,并不是我们会以为的上层。这个接口可以看成下层提供的一个Facade。实际的依赖关系仍然是上层依赖下层(物理依赖关系),我认为这跟DIP原则(逻辑依赖关系)并不矛盾。
/*A.h*/
#include "B.h"
class A
{
public:
void f(B* b)
{
b->g();
}
};
我们经常会写这种代码。不是吗?
现在如果B.h文件做了修改,那么类A肯定要重新编译。最主要的是,类A直接使用了类B的方法,而类B是个具体类,假设现在有一个新的类C,它也同样实现了g(),我们想改成:
void f(C* b)
{
b->g();
}
可以看到,我们必须修改类A中函数f()的实现代码才能实现我们的目的。
但并不是什么代码都能轻易修改的。所以更好的解决办法就是利用DIP原则,让类A和类B共同依赖接口g()。只要接口g()不改变,那么我们就可以很容易的用不同的实现来代替,只要它们有相同的接口即可。
例如:
/*binterface.h*/
class binterface
{
public:
virtual void g()=0;
};
/*A.h*/
#include "binterface.h"
class A
{
public:
void f(binterface* b)
{
b->g();
}
};
现在A只依赖binterface了,只要类B和类C都实现了这个接口,那么我们就可以灵活的在类A中使用,同时不用修改类A的代码!
还有个问题,现在抽取出接口,那么这个接口从逻辑上讲到底是属于上层还是下层?(这可能会影响到文件的物理位置和包的接口)
根据专家的讲法,这个接口仍然属于下层,并不是我们会以为的上层。这个接口可以看成下层提供的一个Facade。实际的依赖关系仍然是上层依赖下层(物理依赖关系),我认为这跟DIP原则(逻辑依赖关系)并不矛盾。
我们更希望重用的是高层的策略设置模块。我们已经非常擅长通过子程序库的形式来重用低层模块。如果高层模块依赖于低层模块,那么在不同的上下文中重用高层模块将变得非常困难。然而,如果高层模块独立于低层模块, 那么高层将变得非常容易重用。
该原则是做“Framework”设计的核心。
解决依赖这个问题的本质是使用抽象。我们让高层模块依赖于抽象(抽象类或者接口),而让低层模块来实现抽象。从原理上, 这个原则就是:
1) 任何变量都不能拥有一个具体类的指针或者引用。
2)任何类都不应该从具体类派生
3)任何方法都不应该覆写基类中已经实现的方法。
这个原则对于那些虽然具体但是却稳定的类来说似乎并不是很合适, 如果一个类不太会改变, 而且也不太可能创建其他的派生类,那么依赖它似乎并没有太大的危害。比如java的String类。