美文网首页
依赖反转设计原则(DIP)

依赖反转设计原则(DIP)

作者: 多关心老人 | 来源:发表于2020-07-22 08:25 被阅读0次

    依赖反转原则(dependency inversion principle)是6大设计原则中比较重要的一个,我认为是最重要的一个,同时也是能颠覆你认知的一个原则。

    DIP规定:

    1. 高层模块不应该依赖低层模块,高层和低层都要依赖抽象
    2. 抽象不要依赖于细节,细节要依赖抽象

    调用别的模块的叫高层,被调用模块的叫低层。

    由于目前MVC架构模式和Spring框架很流行,大家写代码都是Controller里注入Service,Service里注入DAO,如果是简单项目,这样做无可厚非,MVC简单易懂,实现起来很容易,但是这种设计中业务比较复杂多变的时候就Hold不住了,当低层如DAO, Sevice发生变化,高层也要做改动,这样的系统稳定性很弱,扩展性也很弱。

    当然不是说MVC这种架构模式是错的,只是说我们用Controller & Sevice & DAO实现错了,更好的做法可以用六边形架构模式和DDD思想。

    好了,现在来解释下DIP的2个原则是什么意思以及为什么说DIP会颠覆认知。
    还是上面的例子,我们先跳过Controller, 拿Service和DAO来说,如果张三是Service开发人员,李四是DAO的开发人员,张三在Service里依赖一个Repository接口(Repository接口是张三的),李四的DAO实现了Repository接口,这样张三的代码和李四的代码没有依赖,李四改了DAO的任何代码对张三都是无感知,只要李四实现了张三需要的接口。这个例子中日常生活中也很常见,我需要你为我提供一个东西,接口规范是这样的,只要你生产的东西符合我要的规范,我根本不用管你则么实现的,耦合就是这样被解开了。

    1. 高层模块不应该依赖低层模块,高层和低层都要依赖抽象

    Service是高层模块,DAO是低层模块,Service不要依赖DAO而是要依赖Repository,同时DAO实现Repository。

    2. 抽象不要依赖于细节,细节要依赖抽象

    细节就是具体实现,DAO就细节,Repository是抽象。

    OK,到这里还有一个问题,Repository应该由谁定义,即谁拥有Repository的定义权?
    答案是Service,而不是DAO。我们知道是MVC模式中,Service负责处理业务逻辑,DAO负责数据持久化的,持久化可以有多种方式,DAO可以有自己的接口和N多实现类,但是你要实现Service的接口,这样就能为Service提供服务了。如果Repository交由DAO去定义,那么DAO定义出来的接口一定是对存储层的抽象而不是对业务持久化的抽象,这样的接口给Sevice用,就会造成Service迁就DAO的代码写一些与业务无关的代码了。
    做业务逻辑开发的,需要知道数据是怎么存储的吗?
    答案是不需要,站在抽象的角度来说,Service把逻辑处理完,数据拼好,扔给存储层就好,不用管是存在RDB里还是NoSQL里,甚至你存在内存或文件里都行。简单点来说Service需要的时候DAO能取出来/存进去就行。
    所以说Repository交由Service定义更好,Service需要什么样的接口就定义什么样的接口,具体实现交给DAO好了,不要一边写着业务逻辑,一边还要考虑数据怎么存储。
    写到这里,有没有感觉DIP颠覆了你的认知,如果你很早就理解并运用了DIP,那恭喜你,你比同龄人更早迈入软件设计的大门。

    不要把DIP和Spring的IOC(inversion of control)弄混,IOC是指bean的生命周期交由Spring框架控制。

    有没有觉得疑惑,为什么前面只说了Service & DAO,跳过了Controller, 如果按DIP思想,那Controller是不是也要定义个接口,由Service实现呢?
    这是一个好问题,答案是不需要也不能,这里牵扯一个领域设计问题,领域是什么意思?在动物界就是领地的意思,这块区域是属于这只动物的,别的动物不能来这块区域生活,在软件开发就是业务,就是问题域,例如在电商系统里,有商品、库存、订单等等模块,每一个模块就构成了一个领域。基于一个领域开发的系统,在内部是高度聚合的,同时对外提供服务方法。在上面的例子中,Service和DAO是一个领域内的,而Controller不是,在六边形架构中,Controller属于适配层,负责接收用户请求参数、校验参数、组装Service需要的参数,然后调用Service拿到结果再返回给用户。目前流行http rest这种请求方式,说不定哪天系统就要支持TCP请求方式,可能哪天又要增加MQ消费方式呢,这些都是适配层,不管增加多少种适配方式,领域是不用动的。

    前面说的如果DAO实现了Repository接口,那高层定义的接口就侵入了低层的实现,低层的实现就不好通用了,怎么办呢? 答案就是不要通用,订单模块的OrderDAO就服务于OrderService,不要考虑OrderDAO服务于ProductService,在现实开发过程中,如果你写了一个通用的DAO,能存储所有模块的数据,如果你的系统很简单,这么写无所谓,系统一旦变复杂了,你就需要重写,不然代码会变的很乱,非常不好维护。因为在大的系统中,各个模块(业务领域)是分开的,变成分布式系统,甚至订单和产品的存储方式都不一样,订单需要强事务的RDB支持,商品对强事务就没有这么高的要求。DAO是领域层的一部分,需要配合并支持领域服务,如在Repository中定义了一个方法save(order),那么在存储的时候除了要存订单的id,单号、客户电话、送货地址、金额,还要存储订单明细,这些信息是在不同的表里,就需要住DAO中多次调用数据库了,千万不要反过来在Service里调多次DAO。
    那可能你要说了,我整个系统使用的都是RDB,难道我不需要一个通用的调用RDB的DAO吗?答案是需要,但是这块基本上不用我们写了,ORM框架已经封装了,我们只需要在DAO中调用一下就好,基本就是1行代码的事,不要认为我这里说的DAO是负责操作RDB的crud,调用ORM太简单了,没必要就1~2行代码而抽出一个DAO层。DAO是为Service持久化服务的。

    使用DIP的框架有哪些呢?
    JDBC, Servlet这些都是,第三方定义了接口,业务应用依赖这些接口,同时低层实现模块(如mysql jdbc驱动)和servlet容器实现了这些接口,这样我们的业务应用就能直接使用Jdbc和servlet,而不用管具体的低层实现。
    Spring框架也是DIP的实现,Spring定义了一些接口,业务应用实现了这些接口,Spring就能调用我们的业务代码了。

    在业务开发中,我们也可以像Spring一样定义一些接口,让那些为我们提供服务的实现这些接口,他们的代码能可以嵌入到我们系统中使用了。在框架开发中,我们定义一些接口,使用我们框架的应用实现这些接口,他们的代码就能被框架调度了,这就是Spring干的事,当时使用接口有一定的侵入性,Spring提供了注解,侵入性更小了。如果你想要制定标准,可以把接口单独定义出来,让业务和框架都实现你定义的接口, 这就是Sevlet规范干的事。

    相关文章

      网友评论

          本文标题:依赖反转设计原则(DIP)

          本文链接:https://www.haomeiwen.com/subject/wxgakktx.html