六、设计模式的六大设计原则之依赖倒置原则(DIP, Dependence Inversion Principle)

1. 何为依赖倒置原则

定义:高层模块不应该依赖于低层模块,二者都应该依赖于抽象;抽象不应该依赖细节;细节应该依赖抽象。

定义解读:

依赖倒置原则在程序编码中经常运用,其核心思想就是面向接口编程,高层模块不应该依赖低层模块(原子操作的模块),两者都应该依赖于抽象。我们平时常说的“针对接口编程,不要针对实现编程”就是依赖倒转原则的最好体现:接口(也可以是抽象类)就是一种抽象,只要不修改接口声明,大家可以放心大胆调用,至于接口的内部实现则无需关心,可以随便重构。这里,接口就是抽象,而接口的实现就是细节。

如果不管高层模块还是底层模块,它们都依赖于抽象,具体一点就是接口或者抽象类,只要接口是稳定的,那么任何一个的更改都不用担心其他受到影响,这就使得无论高层模块还是低层模块都可以很容易地被复用。

依赖倒转原则其实可以说是面向对象设计的标志,用哪种语言来编写程序不重要,如果编写时考虑的都是如何针对抽象编程而不是针对细节编程,即程序中所有的依赖关系都是终止于抽象类或者接口,那就是面向对象的设计,反之那就是过程化的设计(说这句话可能不怎么好理解,再加上一句话就好理解了:面向对象的设计,出发点就是应对变化的问题)。

再举一个生活中的例子,电脑中内存或者显卡插槽,其实是一种接口,而这就是抽象;只要符合这个接口的要求,无论是用金士顿的内存,还是其它的内存,无论是4G的,还是8G的,都可以很方便、轻松的插到电脑上使用。而这些内存条就是具体实现,就是细节。

错误做法:抽象A依赖于实现细节b,如图1-1所示:

图1-1

正确做法:抽象A依赖于抽象B,实现细节b实现抽象B,如图1-2所示:

图1-2

优点:代码结构清晰,维护容易。

2. 情景设置

类A直接依赖类B,假如需要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。

解决方案

将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率。

依赖倒置原则基于这样一个事实:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建起来的架构比以细节为基础搭建起来的架构要稳定的多。在C#/Java中,抽象指的是接口或者抽象类;在Objective-C中,抽象指的是委托,细节就是具体的实现类,使用接口或者抽象类的目的是制定好规范和契约,而不去涉及任何具体的操作,把展现细节的任务交给它们的实现类去完成。

3. 代码示例

继续发工资的场景:这里,类SalaryManage(类似上面说的类A)负责工资的管理;Director(类似上面说的类B)是总监类,现在我们要通过SalaryManage类来给总监发放工资了,主要代码片段如下所示:

(1)EmployeeDelegate

@protocol EmployeeDelegate <NSObject>

- (void)calculateSalary;

@end

(2)Director

@interface Director : NSObject<EmployeeDelegate>

@property(nonatomic, copy) NSString *strName;  // 姓名

//- (void)calculateSalary;

@end


@implementation Director

@synthesize strName = _strName;

- (void)calculateSalary
{
    NSLog(@"%@总监的工资是10000",_strName);
}
@end

(3)Manager

@interface Manager : NSObject<EmployeeDelegate>

@property(nonatomic, copy) NSString *strName;  // 姓名

//- (void)calculateSalary;

@end



@implementation Manager

@synthesize strName = _strName;

- (void)calculateSalary
{
    NSLog(@"%@经理的工资是1000",_strName);
}
@end

(4)SalaryManage

@interface SalaryManage : NSObject

//- (void)calculateSalary:(Director *)director;  // 原始做法
- (void)calculateSalary:(id<EmployeeDelegate>)employee;  // 遵守依赖倒置原则的做法

@end


@implementation SalaryManage

/*
- (void)calculateSalary:(Director *)director
{
    [director calculateSalary];
}
 */

- (void)calculateSalary:(id<EmployeeDelegate>)employee
{
    [employee calculateSalary];
}

@end

(5)客户端调用

/* 原始做法
Director *director = [[Director alloc] init];
director.strName  = @"张三";
SalaryManage *salaryManage = [[SalaryManage alloc] init];
[salaryManage calculateSalary:director];
*/
       
// 遵守依赖倒置原则的做法
Director *director = [[Director alloc] init];
director.strName  = @"张三";
 Manager *manager = [[Manager alloc] init];
manager.strName  = @"李四";
SalaryManage *salaryManage = [[SalaryManage alloc] init];
[salaryManage calculateSalary:director];
[salaryManage calculateSalary:manager];

这样给总监发放工资的功能已经很好的实现了,现在假设需要给经理发工资,我们发现工资管理类SalaryManage没法直接完成这个功能,需要我们添加新的方法,才能完成。再假设我们还需要给普通员工、财务总监、研发总监等更多的岗位发送工资,那么我们就只能不断的去修改SalaryManage类来满足业务的需求。产生这种现象的原因就是SalaryManage与Director之间的耦合性太高了,必须降低它们之间的耦合度才行。因此我们引入一个委托EmployeeDelegate,它提供一个发放工资的方法定义,然后我们让具体的员工类Director、Manager等都实现该委托方法。

这样修改后,无论以后怎样扩展其他的岗位,都不需要再修改SalaryManage类了。代表高层模块的SalaryManage类将负责完成主要的业务逻辑(发工资),如果需要对SalaryManage类进行修改,引入错误的风险极大。所以遵循依赖倒置原则可以降低类之间的耦合性,提高系统的稳定性,降低修改程序造成的风险。

同样,采用依赖倒置原则给多人并行开发带来了极大的便利,比如在上面的例子中,刚开始SalaryManage类与Director类直接耦合时,SalaryManage类必须等Director类编码完成后才可以进行编码和测试,因为SalaryManage类依赖于Director类。按照依赖倒置原则修改后,则可以同时开工,互不影响,因为SalaryManage与Director类一点关系也没有,只依赖于协议(Java和C#中称为接口)EmployeeDelegate。参与协作开发的人越多、项目越庞大,采用依赖导致原则的意义就越重大。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容