谈一谈依赖倒置原则

2019-11-20 21:34:11 浏览数 (1)

一、概念


依赖倒置原则(Dependence Inversion Principle,DIP)是指设计代码结构时,高层模块不应该依赖低层模块,二者都应该依赖其抽象。

抽象不应该依赖细节,细节应该依赖抽象。通过依赖倒置,可以减少类与类之间的耦合性,提高系统的稳定性,提高代码的可读性和可维护性,并且能够降低修改程序所造成的风险。

二、为什么


先来看一个例子

可是依赖倒置原则是怎么做到的呢?我们先来看一个例子:一个爱学习的「我没有三颗心脏」同学现在正在学习「设计模式」和「Java」的课程,伪代码如下:

代码语言:javascript复制
public class Wmyskxz {

    public void studyJavaCourse() {
        System.out.println("「我没有三颗心脏」同学正在学习「Java」课程");
    }

    public void studyDesignPatternCourse() {
        System.out.println("「我没有三颗心脏」同学正在学习「设计模式」课程");
    }
}

我们来模拟上层调用一下:

代码语言:javascript复制
public static void main(String[] args) {
    Wmyskxz wmyskxz = new Wmyskxz();
    wmyskxz.studyJavaCourse();
    wmyskxz.studyDesignPatternCourse();
}

原因一:有效控制影响范围

由于「我没有三颗心脏」同学热爱学习,随着学习兴趣的 “暴增”,可能会继续学习 AI(人工智能)的课程。这个时候,因为「业务的扩展」,要从底层实现到高层调用依次地修改代码。

我们需要在 Wmyskxz 类中新增 studyAICourse() 方法,也需要在高层调用中增加调用,这样一来,系统发布后,其实是非常不稳定的。显然在这个简单的例子中,我们还可以自信地认为,我们能 Hold 住这一次的修改带来的影响,因为都是新增的代码,我们回归的时候也可以很好地 cover 住,但实际的情况和实际的软件环境要复杂得多。

最理想的情况就是,我们已经编写好的代码可以 “万年不变”,这就意味着已经覆盖的单元测试可以不用修改,已经存在的行为可以保证保持不变,这就意味着「稳定」。任何代码上的修改带来的影响都是有未知风险的,不论看上去多么简单。

原因二:增强代码可读性和可维护性

另外一点,你有没有发现其实加上新增的 AI 课程的学习,他们三节课本质上行为都是一样的,如果我们任由这样行为近乎一样的代码在我们的类里面肆意扩展的话,很快我们的类就会变得臃肿不堪,等到我们意识到不得不重构这个类以缓解这样的情况的时候,或许成本已经变得高得可怕了。

原因三:降低耦合

《资本论》中有这样一段描述:

在商品经济的萌芽时期,出现了物物交换。假设你要买一个 iPhone,卖 iPhone 的老板让你拿一头猪跟他换,可是你并没有养猪,你只会编程。所以你找到一位养猪户,说给他做一个养猪的 APP 来换他一头猪,他说换猪可以,但是得用一条金项链来换...

所以这里就出现了一连串的对象依赖,从而造成了严重的耦合灾难。解决这个问题的最好的办法就是,买卖双发都依赖于抽象——也就是货币——来进行交换,这样一来耦合度就大为降低了。

三、怎么做


我们现在的代码是上层直接依赖低层实现,现在我们需要定义一个抽象的 ICourse 接口,来对这种强依赖进行解耦(就像上面《资本论》中的例子那样):

接下来我们可以参考一下伪代码,先定一个课程的抽象 ICourse 接口:

代码语言:javascript复制
public interface ICourse {
    void study();
}

然后编写分别为 JavaCourseDesignPatternCourse 编写一个类:

代码语言:javascript复制
public class JavaCourse implements ICourse {

    @Override
    public void study() {
        System.out.println("「我没有三颗心脏」同学正在学习「Java」课程");
    }
}

public class DesignPatternCourse implements ICourse {

    @Override
    public void study() {
        System.out.println("「我没有三颗心脏」同学正在学习「设计模式」课程");
    }
}

然后把 Wmyskxz 类改造成如下的样子:

代码语言:javascript复制
public class Wmyskxz {

    public void study(ICourse course) {
        course.study();
    }
}

再来是我们的调用:

代码语言:javascript复制
public static void main(String[] args) {
    Wmyskxz wmyskxz = new Wmyskxz();
    wmyskxz.study(new JavaCourse());
    wmyskxz.study(new DesignPatternCourse());
}

这时候我们再来看代码,无论「我没有三颗心脏」的兴趣怎么暴涨,对于新的课程,都只需要新建一个类,通过参数传递的方式告诉它,而不需要修改底层的代码。实际上这有点像大家熟悉的依赖注入的方式了。

总之,切记:以抽象为基准比以细节为基准搭建起来的架构要稳定得多,因此在拿到需求后,要面相接口编程,先顶层设计再细节地设计代码结构。

0 人点赞