面向对象设计原则实践.之二.依赖倒转原则

二、依赖倒转原则(DIP--Dependency Inversion Principle)

1.依赖倒转原则定义

a). 高层模块不应该依赖低层模块,它们都应该依赖抽象。

抽象不应该依赖于细节,细节应该依赖于抽象。

b). 要针对接口类/抽象类编程,不要针对实现编程。 

 

高层模块包含了一个应该程序中的重要的策略选择和业务模型,

正是这些高层模块才使得其所有的应用程序区别于其他,

如果高层依赖于低层,那么对低层模块的改动就会直接影响到高层模块,从而迫使它们依次做出改动。 

 

2. 依赖倒转原则分析

a)  简单来说,依赖倒转原则就是指:

代码要依赖于抽象的类,而不要依赖于具体的类;

要针对接口或抽象类编程,而不是针对具体类编程。

b)  实现开闭原则的关键是抽象化,并且从抽象化导出具体化实现,

如果说开闭原则是面向对象设计的目标的话,那么依赖倒转原则就是面向对象设计的主要手段。

c) 依赖倒转原则的常用实现方式之一是:

在代码中使用抽象类,而将具体类放在配置文件中。

d) 类之间的耦合:

> 零耦合关系 : 无关系

> 具体耦合关系  : 强关系

> 抽象耦合关系。     : 弱关系

  依赖倒转原则要求客户端与实现类之间是 通过抽象类进行耦合。

以抽象耦合是依赖倒转原则的关键。

e)  依赖注入:

构造注入:通过构造函数注入实例变量

设值注入:通过Setter方法注入实例变量

接口注入:通过接口方法注入实例变量

 

3. 实例一

某系统提供一个数据转换模块,可以将来自不同数据源的数据转换成多种格式,如,

可以转换来自数据库的数据(DatabaseSource)、也可以转换来自文本文件的数据(TextSource),

转换后的格式可以是XML文件(XMLTransformer)、也可以是XLS文件(XLSTransformer)

依赖倒转原则-图-1

依赖倒转原则-图-2

图(一)和图(二)分析:

因为该系统可能需要增加新的数据源或者新的文件格式,

(图一)中,每增加一个新的类型的数据源或者新的类型的文件格式,

客户类MainClass都需要修改源代码,以便使用新的类,这违背了开闭原则。

(图二)使用依赖倒转原则对其进行重构:

a) 客户类MainClass 依赖于两个抽象类变量: AbstractSource, AbstractTransformer;

其中,抽象类AbstractSource的子类 DatatbaseSource , TextSource是不同输出入源的实现;

抽象类AbstractTransformer的子类XMLTransformer, XLSTransformer是不同输出格式的实现。

而客户类通过配置文件config.xml来确定到底如何处理数据;

 

 

4. 实例二

反面例子: 

缺点:耦合太紧密,Light发生变化将影响ToggleSwitch。 

 

解决办法一: 

将Light作成Abstract,然后具体类继承自Light。 

优点:ToggleSwitch依赖于抽象类Light,具有更高的稳定性,

而BulbLight与TubeLight继承自Light,可以根据"开放-封闭"原则进行扩展。

只要Light不发生变化,BulbLight与TubeLight的变化就不会波及ToggleSwitch。 

 

缺点:如果用ToggleSwitch控制一台电视就很困难了。

总不能让TV继承自Light吧。 

 

解决方法二: 

优点:更为通用、更为稳定。 

 

上面的计算器例子用这个原则更进一步的优化的代码如下:

#include <stdio.h>

#include <stdlib.h>

#include <iostream>

#include <string>



using namespace std;



/* Step1:

* 使用vitual定义函数的功能模块,相当于实现公共界面设计

*/

class IGetResult {

public:

//virtual double get_result(double numberA, double numberB) = 0;

virtual double get_result() = 0;



public:

/* 注意一定要不要忘记添加虚析构函数 */

virtual ~IGetResult(){}

};



/* Step2 :

* 继承基类,定义具体的功能模块,并声明具体需要的私有成员变量

* 实现具体的功能模块

*/

class Add : public IGetResult {

public:

Add(double a, double b){

std::cout << "Add" << endl;



m_numberA = a;

m_numberB = b;

};



~Add(void) {};



public:

double get_result();

private:

double m_numberA;

double m_numberB;

};



/* 实现具体的功能模块 */

double Add::get_result() {

std::cout << "Add::get_result()" << endl;



return m_numberA + m_numberB;

}



class Del : public IGetResult {

public:

Del(double a, double b){

std::cout << "Del" << endl;

m_numberA = a;

m_numberB = b;

};



virtual ~Del(void) {};



public:

double get_result();



private:

double m_numberA;

double m_numberB;

};



double Del::get_result(){

std::cout << "Del::get_result()" << endl;



return m_numberA - m_numberB;

}



class Mul: public IGetResult {

public:

Mul(double a, double b){

std::cout << "Mul" << endl;

m_numberA = a;

m_numberB = b;

};

virtual ~Mul(void) {};

public:

double get_result();

private:

double m_numberA;

double m_numberB;

};

double Mul::get_result(){

std::cout << "Mul::get_result()" << endl;



return m_numberA * m_numberB;

}



/* Step4:

* 将功能函数接口注入封装类中

*/

class MainOperate {

public:

MainOperate(IGetResult *get_result_ptr){

this->m_get_result = get_result_ptr;

}

public:

double Operate();

private:

IGetResult *m_get_result;

};



/* 实现封装类中的接口函数的具体调用方式 */

double MainOperate::Operate(){

double ret = 0;

ret = this->m_get_result->get_result();

return ret;

}







int main(int argc, char* argv[]){

std::cout << "main()" << endl;

int a = 22;

int b = 10;

int ret = 0;

/* Step5: 主函数中定义调用模块接口函数*/

IGetResult *get_result = new Add(a, b);



/* Step6: 主函数调用封装类中接口函数*/

MainOperate *main_operate = new MainOperate(get_result);

ret = main_operate->Operate();

std::cout << "ret = " << ret << endl;

delete get_result;



get_result = new Del(a, b);

main_operate = new MainOperate(get_result);

ret = main_operate->Operate();

std::cout << "ret = " << ret << endl;

delete get_result;

get_result = new Mul(a, b);

main_operate = new MainOperate(get_result);

ret = main_operate->Operate();

std::cout << "ret = " << ret << endl;

delete get_result;



/*

Add add = Add(a, b);

ret = add.get_result();

std::cout << "ret = " << ret << endl;



Del del = Del(a, b);

ret = del.get_result();

std::cout << "ret = " << ret << endl;

*/

return 0;

}

特点:

代码要依赖于抽象的类,而不要依赖于具体的类;

针对接口或抽象类编程,而不是针对具体类编程。

实现了接口注入;

 

 

5. 总结

高层模块不应该依赖底层模块,两个都应该依赖与抽象;

抽象不应该依赖于细节,细节应该依赖于抽象。

客户端与实现类通过抽象类进行耦合。

 

DIP优点: 

使用传统过程化程序设计所创建的依赖关系,策略依赖于细节,这是糟糕的,因为策略受到细节改变的影响。

依赖倒置原则使细节和策略都依赖于抽象,抽象的稳定性决定了系统的稳定性。 

 

DIP注意点:

1)、任何变量都不应该持有一个指向具体类的指针或者引用 

2)、任何类都不应该从具体类派生(始于抽象,来自具体) 

3)、任何方法都不应该覆写它的任何基类中的已经实现了的方法 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

北雨南萍

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值