03.依赖倒置原则(Dependence Inversion Principle)

概述

高层模块不应依赖低层模块,二者都应该依赖其抽象。而抽象不应依赖细节,细节应该依赖抽象。依赖倒置原则的中心思想其实就是面向接口编程

相对于细节的多变性,抽象的东西会稳定的多,所以以抽象为基础搭建的架构自然也会比以细节为基础搭建的架构稳定的多
使用接口或抽象类的目的是为了更好的制定规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。

相信有读过spring framework源码的同学应该对这一点深以为是,比如其核心接口之一的BeanFactory接口之下就继承了AutowireCapableBeanFactory、HierarchicalBeanFactory、ListableBeanFactory接口,而这些接口又被多个抽象类有选择实现,正是对依赖倒置原则的应用才使得spring framework 框架具有了极高的健壮性和扩展性。

三寸反骨

我们不妨从相反的角度出发,写个反例看看会有什么问题,今日反骨颇重,就是不想遵循依赖倒置原则。

比如我们现在需要编写一个简单的Person类,让他可以接受邮件消息即可。
反例

java 复制代码
public class DependecyInversion {
    public static void main(String[] args) {
        Person person = new Person();
        person.receive(new Email());
    }
}

class Email{
    public String getInfo(){
        return "电子邮件信息: Hello,Email";
    }
}

class Person {
    public void receive(Email email){
        System.out.println(email.getInfo());
    }
}

编程的过程十分愉快,如此简单的需求甚至不需要聪明的我们多思考一秒。运行效果也完全满足预期。
但是,来了一个不好不坏的消息:"能力需要扩展,客户要求,除了收到email的能力外,微信消息也想收到!"
"反骨仔想了想,不要紧,我新增类,同时Person类也要增加相应的方法就好了呀"!

于是他加班1小时完成了这个需求,自信满满的走了。
但是,第二天又来了个不好不坏的消息:"客户对产品很满意,同时要求除了想收到email、微信消息之外,还想扩展几十个软件的消息,清单包含:"QQ、微博、墨迹天气、钉钉..."

反骨仔爆炸了,因为这个需求如果继续按他的思路去实现,类也爆炸了。实现的方法更是爆炸到难以维护。
所以说一定要设计先行,一个优秀的设计可能会占据相当长的开发时间,但是会为后期的扩展和维护提供强有力的保障!


优化设计

我们把时间推回两天前,接到需求的我们首先便进行了深度剖析并达成了共识:"依赖倒置原则必须遵守!"于是,有了如下代码:

java 复制代码
public class DependecyInversion {
    public static void main(String[] args) {
        Person person = new Person();
        person.receive(new Email());
        person.receive(new WeChat());
    }
}
interface IReceive{
    public String getInfo();
}

class Email implements IReceive{
    public String getInfo(){
        return "电子邮件信息: Hello,Email";
    }
}

class WeChat implements IReceive{
    public String getInfo(){
        return "微信信息: Hello,WeChat";
    }
}

class Person {
    public void receive(IReceive receiver){
        System.out.println(receiver.getInfo());
    }
}

当客户说我要继续扩展几十个消息入口时,我们会发现,我们对于person类不需要做任何改动了,不过是需要遵照IReceive接口规范去实现新扩展的业务细节就可以了。善莫大焉。


依赖关系传递三板斧

常见的依赖关系传递有三种方式:

接口传递

java 复制代码
public class DependencyPass {
    public static void main(String[] args) {
        ChangHong changHongTv = new ChangHong();
        OpenAndClose openAndClose = new OpenAndClose();
        openAndClose.open(changHongTv);
    }
}

class ChangHong implements ITV,ITV2,ITV3{
    @Override
    public void play() {
        System.out.println("长虹电视机打开了。");
    }
}
interface IOpenAndClose{
    public void open(ITV tv);
}
interface ITV{
    public void play();
}
//实现接口
class OpenAndClose implements IOpenAndClose{
    @Override
    public void open(ITV tv) {
        tv.play();
    }
}

构造方法传递

java 复制代码
public class DependencyPass {
    public static void main(String[] args) {
        ChangHong changHongTv = new ChangHong();
        OpenAndClose2 openAndClose2 = new OpenAndClose2(changHongTv);
        openAndClose2.open();
    }

}
class ChangHong implements ITV,ITV2,ITV3{
    @Override
    public void play() {
        System.out.println("长虹电视机打开了。");
    }
}
interface IOpenAndClose2{
    public void open();
}
interface  ITV2{
    public void play();
}
class OpenAndClose2 implements IOpenAndClose2{
    public ITV2 tv;
    public OpenAndClose2(ITV2 tv){
        this.tv = tv;
    }
    @Override
    public void open() {
        this.tv.play();
    }
}

setter方法传递

java 复制代码
public class DependencyPass {
    public static void main(String[] args) {
        ChangHong changHongTv = new ChangHong();
        OpenAndClose3 openAndClose3 = new OpenAndClose3();
        openAndClose3.setTv(changHongTv);
        openAndClose3.open();
    }

}

class ChangHong implements ITV,ITV2,ITV3{
    @Override
    public void play() {
        System.out.println("长虹电视机打开了。");
    }
}
interface IOpenAndClose3{
    public void open();
    public void setTv(ITV3 itv3);
}
interface ITV3{
    public void play();
}
class OpenAndClose3 implements IOpenAndClose3{
    private ITV3 itv3;
    @Override
    public void setTv(ITV3 itv3) {
        this.itv3 = itv3;
    }
    @Override
    public void open() {
        this.itv3.play();
    }
}

  1. 底层模块尽量都要有抽象类或接口,或者两者都有,程序稳定性更好;
  2. 变量的声明类型尽量是抽象类或接口,这样我们的变量引用和实际对象间就存在一个缓冲层,利于程序扩展和优化;
  3. 继承时遵循里氏替换原则;
  4. 什么是里氏替换原则?下次讲!

关注我,共同进步,每周至少一更。------Wayne

相关推荐
一只自律的鸡几秒前
C语言项目 天天酷跑(上篇)
c语言·开发语言
程序猿000001号3 分钟前
使用Python的Seaborn库进行数据可视化
开发语言·python·信息可视化
一个不正经的林Sir8 分钟前
C#WPF基础介绍/第一个WPF程序
开发语言·c#·wpf
愤怒的代码11 分钟前
Spring Boot对访问密钥加解密——HMAC-SHA256
java·spring boot·后端
带多刺的玫瑰12 分钟前
Leecode刷题C语言之切蛋糕的最小总开销①
java·数据结构·算法
API快乐传递者12 分钟前
Python爬虫获取淘宝详情接口详细解析
开发语言·爬虫·python
公众号Codewar原创作者14 分钟前
R数据分析:工具变量回归的做法和解释,实例解析
开发语言·人工智能·python
赵钰老师17 分钟前
基于R语言APSIM模型应用及批量模拟(精细农业、水肥管理、气候变化、粮食安全、土壤碳周转、环境影响、农业可持续性、农业生态等)
开发语言·数据分析·r语言
栗豆包28 分钟前
w118共享汽车管理系统
java·spring boot·后端·spring·tomcat·maven
lly20240634 分钟前
Highcharts 饼图:数据可视化利器
开发语言