六大设计原则之依赖倒置原则(DIP)

很多初学编程的小伙伴在编程时会发现,自己写的类总是频繁的用到(依赖)其他类,一旦被依赖的类需要修改,那么其他的类也统统都要修改一遍,让人感觉烦不胜烦。若是小型的程序也紧紧是觉得烦而已,可一旦是大型的工程,这种强耦合的程序一旦有某一个细节放生改变,那是砸电脑的心都有。

各个具体类之间发生了直接的依赖关系,使得这些类紧紧地耦合在了一起,从而降低了程序的稳定性、可维护性和可读性。要解决这个问题,我们可以用一些方法来将这些程序解耦,降低其耦合性。这种方法就是我即将要讲到的依赖倒置原则(Dependence Inversion Principle ,DIP)

定义

High level modules should not depend upon low level modules.Both should depend upon abstractions.Abstractions should not depend upon details.Details should depend upon abstractions.

高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。

也就是说,高层模块、低层模块、细节都应该依赖抽象

  • 低层模块:组成一个大逻辑的颗粒原子逻辑
  • 高层模块:颗粒原子逻辑组成的模块。
  • 抽象:指接口或抽象类,两者都不能被实例化。
  • 细节:实现类,实现接口或继承抽象类而产生的类就是细节,其特点就是可以直接被实例化。

java的精髓

依赖倒置原则在java中的三种含义:

  1. 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的;
  2. 接口或抽象类不依赖于实现类;
  3. 实现类依赖接口或抽象类。

这三种含义在java的精髓之一——面向接口编程中体现得淋漓尽致。

用与不用依赖倒置原则的对比

既然一开始就说到不用依赖倒置原则有多糟糕,那么下面我们就用一个简单的程序来证明一下。

不使用依赖倒置原则:

程序1

/**
* 大众汽车类
* @author 叶汉伟
*/
public class DaZhong {
    public void run(){
        System.out.println("开大众汽车");
    }
}

/**
 * 司机类
 * @author 叶汉伟
 */
public class Driver {
    public void drive(DaZhong daZhong){
        daZhong.run();
    }
}

public class Client {
    public static void main(String[] args){
        Driver Tom=new Driver();
        DaZhong daZhong=new DaZhong();
        Tom.drive(daZhong);
    }
}

看上去没什么嘛,这不是好好的吗?好像也是哦。那么既然司机会开大众汽车,那应该会开宝马吧。我们让它开一下宝马试试。

先生产一辆宝马给他:

程序2

/**
 * 宝马车类
 * @author 叶汉伟
 */
public class BaoMa {
    public void run(){
        System.out.println("开宝马车");
    }
}

当我们要让司机开宝马的时候,他确开不了,程序报错了。感情他考的是大众驾照,有宝马都开不了啊。

其实要让司机开宝马车也容易,把司机类开车方法的参数改一下就成了呗。的确可以。但又想想,现在知识多了个宝马车,如果我再多个奔驰、本田什么的,那不就是要经常改,大改特改?对于大型的项目来说,这是致命的。

使用依赖倒置原则

那么接下来,我们看一下使用依赖倒置原则有什么优势。

程序3

/**
 * 车子接口
 * @author 叶汉伟
 */
public interface ICar {
    public void run();
}

/**
 * 大众汽车类
 * @author 叶汉伟
 */
public class DaZhong implements ICar{
    public void run(){
        System.out.println("开大众汽车");
    }
}

/**
 * 宝马车类
 * @author 叶汉伟
 */
public class BaoMa implements ICar{
    public void run(){
        System.out.println("开宝马车");
    }
}

/**
 * 司机接口
 * @author 叶汉伟
 */
public interface IDriver {
    public void drive(ICar car);
}

/**
 * 司机类
 * @author 叶汉伟
 */
public class Driver implements IDriver{
    public void drive(ICar car){
        car.run();
    }
}

public class Client {
    public static void main(String[] args){
        IDriver Tom=new Driver();
        //Tom开大众汽车
        ICar daZhong=new DaZhong();
        Tom.drive(daZhong);
        //Tom开宝马
        ICar baoMa=new BaoMa();
        Tom.drive(baoMa);
    }
}

看,现在不仅可以开大众,而且可以开宝马了。要还有什么本田、奔驰汽车,一个drive方法都能开,而开其他的车只需要修改一下客户端就可以了。

上面的程序在实现类之间不发生依赖关系,他们的依赖关系是在接口处发生的,这样就可以大大的降低了类之间的耦合。其实这里不仅用到了依赖倒置原则,还用到了里氏替换原则,从类Client中可以看出来。

实现依赖的三种方法

对象的依赖关系可以通过三种方法来实现:

  1. 接口声明依赖对象
  2. 构造函数传递依赖对象
  3. setter方法传递依赖对象

接口声明依赖对象

在接口处就声明了依赖的对象。如程序3中的司机接口IDriver,其方法drive()的形参是ICar类型的。那么我们可以说IDrive与ICar放生了依赖关系,这个依赖对象在接口处已经声明了。接口声明依赖的方法也叫接口注入

构造函数传递依赖对象

在类中通过构造函数声明依赖对象。具体实现如下:

程序4

/**
 * 司机接口
 * @author 叶汉伟
 */
public interface IDriver {
    public void drive();
}

/**
 * 司机类
 * @author 叶汉伟
 */
public class Driver implements IDriver{
private ICar car;
//通过构造函数注入依赖对象
public Driver(ICar car){
    this.car=car;
}
    public void drive(){
        this.car.run();
    }
}

如果我们想要司机开宝马车,只需要将宝马车对象传入构造函数即可。这种方法又叫做构造函数注入

setter方法传递依赖对象

这种方法通过在抽象中增加一个setter方法实现。具体实现如下:

程序5

/**
 * 司机接口
 * @author 叶汉伟
 */
public interface IDriver {
public void setCar(ICar car);
    public void drive();
}

/**
 * 司机类
 * @author 叶汉伟
 */
public class Driver implements IDriver{
private ICar car;
//setter方法传递依赖对象
public void setCar(ICar car){
    this.car=car;
}
    public void drive(){
        this.car.run();
    }
}

若要司机开那种车,只需要将车子的对象通过Driver对象的setCar()方法传入即可。这种方法又叫setter依赖注入

总结

通过依赖倒置原则,我们可以实现模块中的松耦合。具体来说总结为一下几点规则:

  • 每个类尽量都有接口或抽象类,或者抽象类和接口两者都具备
  • 变量的表面类型尽量是接口或者是抽象类
  • 任何类都不应该从具体类派生
  • 尽量不要覆写基类的方法
  • 结合里氏替换原则使用

还没有形成面向接口编程的java学习者们,赶紧将这个规则用起来吧,用过一段之间,你会感觉水平飙升,在代码间游走更加柔韧有余。

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

推荐阅读更多精彩内容