软件设计原则之——依赖倒置原则

什么是依赖倒置?直接上概念的话,非常不好理解。首先要知道什么是依赖?我们上班要坐车,吃饭需要用碗筷,我需要使用这些东西才能达成某项目的,那么我就是依赖于这些东西。反映在代码内就是A类使用B类。下面的代码Worker工人就是依赖于Tool工具。

public class Worker {
    private void doWork(Tool tool){
        tool.doSometing();
    }
}

然后我们看一个例子,程序员小李子写代码就会需要用各种IDE。假设小李子需要写Java代码,那他就需要一个Eclipse来敲代码,小李子依赖的是Eclipse这一具体的工具,代码可以这么写。

 

public class XiaoLiZi {
    private void coding(Eclipse eclipse){
        eclipse.edit();
    }

    public static void main(String[] args){
        //让小李子写Java代码
        XiaoLiZi xiaoliZi = new XiaoLiZi();
        Eclipse eclipse = new Eclipse();
        xiaoliZi.coding(eclipse);
    }
}

由于公司人力紧张,小李子被临时抽调到了前端项目组做前端开发了,这时候他需要一个VsCode来敲代码。由于小李子之前用的是Eclipse,我们除了调整调用处的main方法内的代码,还不得不对小李子的类进行改写。调整coding方法的入参类型为VsCode,这个时候小李子依赖的是另一个具体的工具VsCode。

public class XiaoLiZi {
    private void coding(VsCode vsCode){
        vsCode.edit();
    }

    public static void main(String[] args){
        //让小李子写前端代码
        XiaoLiZi xiaoliZi = new XiaoLiZi();
        VsCode vsCode = new VsCode();
        xiaoliz.coding(vsCode);
    }
}

在金三银四的日子里,小李子的同事跳槽了,这个时候小李子不得不再兼顾回java的开发,现在小李子需要同时编写java代码和前端代码了。现在可就不再是简单调整coding方法的入参类型就能解决的了,XiaoliZi类现有的结构显然不满足让小李同时敲java代码和前端代码的功能,于是我们需要对XiaoLiZi类进行大改,把coding方法拆分成codeJava和codeHtml两个方法,分别传入Eclipse和VsCode。

public class XiaoLiZi {
    private void codeHtml(VsCode vsCode){
        vsCode.edit();
    }

    private void codeJava(Eclipse eclipse){
        eclipse.edit();
    }
    
    public static void main(String[] args){
        XiaoLiZi xiaoliZi = new XiaoLiZi();
        //让小李子写前端代码
        VsCode vsCode = new VsCode();
        xiaoliZi.codeHtml(vsCode);
        //让小李子写java代码
        Eclipse eclipse = new Eclipse();
        xiaoliZi.codeHtml(eclipse);
    }
}

每次变更,我们都需要调整XiaoliZi类的代码,使用了XiaoLiZi类的代码也需要调整。一个小小的改动,影响的范围可能扩散到整个模块,有很大的影响扩散风险,原因也很简单,因为XiaoLiZi这个类和具体的IDE类Eclipse和VsCode是高度耦合的。而抽象是一种很好的解耦方法。什么是抽象?我的理解抽象其实就是概括了某一些事物总体特质的一个范围。汽车是宝马奔驰的抽象,IDE是Eclipse,VsCode的抽象。

现在我们使用抽象的思想来重构代码,满足上面的场景。我们先定义IDE的抽象接口,不管是Eclispe和VsCode都实现了IDE接口,因为他们都有edit功能。

public interface IDE {
    void edit();
}
public class Eclipse implements  IDE{
    @Override
    public void edit() {
        System.out.println("Eclispe 写Java");
    }
}
public class VsCode implements IDE{
    @Override
    public void edit() {
        System.out.println("VsCode 写前端");
    }
}

这个时候,如果我们想让小李子写Java代码可以这么写,注意这个时候XiaoLiZi类依赖的不再是具体的编辑器类,而是IDE这个抽象接口。

public class XiaoLiZi {
    //传入的是IDE,依赖的不再是具体的编辑器类,而是IDE接口
    private void coding(IDE ide){
        ide.edit();
    }

    public static void main(String[] args){

        XiaoLiZi xiaoliZi = new XiaoLiZi();
        //让小李子写java代码,使用了抽象句柄IDE而不是具体类的句柄Eclipse
        IDE eclipse = new Eclipse();
        xiaoliZi.coding(eclipse);
    }
}

当我想让小李子写前端代码时,我只需要给XiaoLiZi一个VsCode实例就行,不需要调整XiaoLiZi类的代码。

public class XiaoLiZi {
    //传入的是IDE,依赖的不再是具体的编辑器类,而是IDE接口
    private void coding(IDE ide){
        ide.edit();
    }

    public static void main(String[] args){

        XiaoLiZi xiaoliZi = new XiaoLiZi();
        //让小李子写java代码,使用了抽象句柄IDE而不是具体类的句柄Eclipse
        IDE eclipse = new Eclipse();
        xiaoliZi.coding(eclipse);

        //让小李子写前端代码,只需要新增两行代码,不改动原有代码
        IDE vsCode = new VsCode();
        xiaoliZi.coding(vsCode);
    }
}

对比下来,第二种写法,对原有功能模块的影响显然是更低的,因为他不需要改动XiaoLiZi类的定义,不管我以后加多少种编辑器,都不需要改动XiaoLiZi的代码。这一改进,得益于我们把XiaoLiZi对具体编辑器的依赖关系给“颠覆”了,XiaoLiZi不依赖于具体的编辑器(Eclipse,VsCode),而是依赖于编辑器的抽象(IDE)。我们放弃了对具体实现细节(类)的依赖关系,转而去依赖抽象(接口)的思想,就是依赖倒置思想。

现在我们可以看下维基百科对“依赖倒置”的定义了:

依赖倒置的概念

 

我的译文:

在面向对象的程序设计中,“依赖倒置”是软件模块解耦的一种具体实现形式。这个原则颠覆了传统的上层业务模块对底层业务模块的依赖关系,致使上层代码不再依赖于底层模块代码的实现细节(而是依赖于底层的抽象)。这个原则要求以下两点:
1.上层模块不能依赖于底层模块(的具体实现细节),他们都要依赖于抽象(接口)。
2.抽象不能依赖于实现细节。实现细节(具体的实现类)需要依赖于抽象(接口)。

Dependency inversion principle wikipedia
In  object-oriented design, the  dependency inversion principle is a specific form of  decoupling software  modules. When following this principle, the conventional  dependency relationships established from high-level, policy-setting modules to low-level, dependency modules are reversed, thus rendering high-level modules independent of the low-level module implementation details. The principle states: [1]
1.High-level modules should not depend on low-level modules. Both should depend on abstractions (interfaces).
2.Abstractions should not depend on details. Details (classes) should depend on abstractions.
 

我们在看各种设计模式的书籍,包括学习Spring AOP的过程中提起IOC的时候,经常会遇到这么一个名词“依赖倒置”。很多人包括我自己在理解这个原则的时候,经常会纠结于“倒置”这个词语,看了好几篇都不能get到这个“倒置”到底是倒置在哪。其实我个人觉得这完全就是一个翻译问题,更好的翻译我觉得应该是“依赖颠覆”。我们传统的直接的思维都是面向实现编程,而面向抽象编程就是对我们传统的面向实现编程的一种颠覆。依赖倒置,说穿了就是面向接口编程,行为类定义尽量是派生自抽象,使用时尽量用抽象的句柄。

抽象的好处

抽象的好处,对大项目而言是非常明显的。抽象是共有特质的概括范围,有了抽象,具体实现细节不会超出抽象的划定范围,不管被依赖的模块是否被完全开发完,依赖他的模块都可以并行的开发。实际上,项目的需求细节的变更也是经常的事情,使用抽象可以减少细节变更导致的代码改动量。具象的细节是多变的,但是基本上不会偏离抽象划定的范围,这样一对比,抽象明显更稳定,更适合作为依赖,就像盖房子一样,根基要稳,抽象就是根基。

 

参考文章:

https://www.cnblogs.com/yulang314/p/3551586.html

https://blog.youkuaiyun.com/briblue/article/details/75093382

https://www.cnblogs.com/fuchongjundream/p/3873073.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值