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

相关推荐
唐青枫15 分钟前
Java Future 与 CompletableFuture 实战指南:从异步结果到任务编排
java
长孙豪翔23 分钟前
在.net中读写config文件的各种方法
java·数据库·.net
tachibana224 分钟前
hot100 回文链表(234)
java·网络·数据结构·leetcode·链表
可乐ea30 分钟前
【Java八股|第10篇】Java 中的包装类和自动拆装箱
java·面试题·包装类·java八股
zfoo-framework41 分钟前
mongo最佳实战(from mongo中文社区)
java
大气的小蜜蜂1 小时前
基于Python+Django的健身房管理系统实现:核心亮点全流程解析
开发语言·python·django
天空'之城1 小时前
Linux 系统编程 04:进程基础
linux·开发语言·进程基础
2zcode1 小时前
免费开源项目文档:基于MATLAB图像处理的药片检测与计数系统设计与实现
开发语言·图像处理·matlab
深盾科技_Virbox1 小时前
加密狗授权能力选型:从授权模型到全生命周期管理
java·网络·数据库
charlie1145141911 小时前
Cinux: 加载第一个内核:从 bootloader 跳进 C++
linux·开发语言·c++·嵌入式