设计模式系列之设计原则(2)依赖倒置原则

定义

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

在依赖倒置原则中,“依赖”不局限于UML中的依赖关系(虚线箭头 局部变量),泛指调用或使用关系,譬如 在A类中用到了B类,则可以简单称A依赖B。

“倒置”体现了遵循依赖倒置原则的设计在类图上表现,从词义表面较难理解究竟 什么东西发生了倒置。需要结合场景,分析发生倒置前后的两种类图,对比理解“倒置”的实际含义。假设tyler学习网课,在学网课有java、python、scala,后续还要学习其他网课。按照面向过程的编程思路,设计业务场景的调用关系如下图所示。

上图中,箭头指向了被依赖方,即tyler依赖了三个具体的实现类。这种设计思路因符合人类的一般思考顺序,在实践中可能会被使用,这就为后期系统的维护扩展带来了困难,特别对于多人合作的大型项目的影响尤为突出。由于tyler和具体的课程产生了强依赖关系,两者具有较高的耦合度。对于后期业务需求变化,譬如要学习新课程go语言,则需要修改tyler的历史代码,此时存在引入新问题的可能性,进而需要对新旧代码重新测试,增加了维护成本。

在了解设计模式相关知识后,可按照依赖倒置原则,重新设计系统的整体结构,如下图所示。我们将所有课程抽象为了接口ICourse,tyler不再直接去调用各个具体的课程,而是直接面向接口编程,这就使tyler和具体课程解耦,新增课程也不用修改tyler的历史代码。这就避免了上一版本的问题。

对比上面两图,图一中高层组件tyler依赖了低层组件(java、python、scala),在图二中高层组件tyler不再依赖低层组件,而是依赖抽象接口ICourse,而低层组件本来是被依赖方,现在也向上依赖了抽象接口ICourse。一般情况下,低层组件都是作为被依赖方,即被箭头所指向,如图一。而在图二中,低层组件的需要依赖抽象接口,即箭头指向其他位置,此时,**依赖发生了倒置,这就是依赖倒置原则中“倒置“的含义**。

上文描述的高层和低层(不是底层)体现在调用或使用关系中,譬如 A 调用B,则可成A为高层,B为低层。这种调用关系也可以理解为依赖关系。

作用

  • 降低类之间的耦合性。通过抽象接口作为中介,使高层调用者 和底层组件解耦。

  • 提高系统稳定性。将业务模型分为变化和稳定两部分,且对变化部分进行抽象封装,使稳定部分只同抽象接口交互,系统较稳定。

    在区分系统中哪部分是变化、哪部分是稳定,需要深入理解业务、搞清楚调用关系;可通过对应关系进行基本确定,比如 甲:乙 = 1:N,则一般来说甲可作为稳定,乙作为变化

  • 提高代码可读性和可维护性。抽象作为交互的中介,抽象的具体实现对高层是透明的,可维护性较好。

实现方法

设计模式一直在提倡针对接口,不针对实现的编程方式,依赖倒置原则正是通过这种方式实现的,但依赖倒置更强调的是抽象,使高低层组件都依赖抽象。在编程实践中,如果遵循以下4点,可以满足依赖倒置原则。

  1. 每个类尽量提供接口或抽象类,或者两者都具备。
  2. 变量的声明类型尽量是接口或者是抽象类。
  3. 任何类都不应该从具体类派生。
  4. 使用继承时尽量遵循里氏替换原则。

代码实践

上图一中面向过程的代码如下,这种方式的问题在于每次需求变化都要修改tyler的代码,稳定性和可扩展性较差。

public class Tyler {
    public void studyJavaCourse(){
        System.out.println("tyler在学习java课程");
    }

    public void studyPythonCourse(){
        System.out.println("tyler在学习python课程");
    }

    //当产生新的课程时,直接在调用者中添加新课程代码
    public void studyScalaCoutse(){
        System.out.println("tyler在学习Scala课程");
    }
}

上图二的代码要分为两个部分,稳定部分(tyler)的代码段如下。

public class Tyler {
    //只针对接口ICoures编程,不关注具体实现
    public void studyImoocCourse(ICourse iCourse){
        iCourse.studyCourse();
    }
}

变化部分的代码分为抽象类(ICourse)和具体实现类(JavaCourse,PythonCourse),如下代码段。

public interface ICourse {
	//接口定义了实现类要完成的动作,是一种锲约和规范
     void studyCourse();
}
public class JavaCourse implements ICourse {
    //实现接口中的定义
    @Override
    public void studyCourse() {
        System.out.println("tyler在学习java课程");
    }
}
public class PythonCourse implements ICourse{
     //实现接口中的定义
    @Override
    public void studyCourse() {
        System.out.println("tyler在学习python课程");
    }
}

编程知识点

  • 在tyler类的代码中,向tyler中传递接口ICourse有三种方式:

    1. 通过构造器
    2. 通过方法参数 (本例采用的方法)
    3. 通过setter方法

    这三种方式是学习过程中的笔记,查阅资料发现这属于spring中的依赖注入相关知识,其内容不是本文重点,不再详述。

参考文献

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值