前言
在计算机世界摸爬滚打多年以后,前辈们发现软件系统总是在不断迭代中,产生了坏味道,让后续的系统变更举步维艰。为了应对呢,他们总结出了几条软件设计的经验,后来被称作SOLID原则:
- Single Responsibility Principle(单一职责原则)
- Open Closed Principle(开闭原则)
- Liskov Substitution Principle(里氏替换原则)
- Interface Segregation Principle(接口隔离原则)
- Dependency Inversion Principle(依赖倒置原则)
依赖倒置原则
定义:高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象
这个原则的实现方法比较简单,本文不做赘述了,主要还是想说一下个人的理解,我认为可以从两个角度进行讨论:分层和抽象。
高层模块不应该依赖低层模块
高层模块不应该依赖低层模块,两者都应该依赖其抽象
在单一职责原则里,我们将职责相同的代码组织到同一个地方,不同职责的代码和模块,构成了不一样的分层。我们以最简单的架构举例:负责处理请求和响应的,归为接口层
;处理核心业务逻辑的,叫业务层
;处理数据库增删改查的,叫仓库层
。
如果按照直觉,或者说数据流向来设计层级之间的依赖,将会是这样:接口层->业务层->仓库层。
不是说这种做法有多大问题,只是它的可修改性会有点差,面临某些变更时可能会难以应对。
请看例子:
假设你在电商业务,商家管理后台中,可以查看商品列表。在项目初期,业务量还很小,直接使用MySQL进行查询。
直觉设计是,仓库层
提供一个Query方法,业务层
代码直接调用:
graph LR
biz --> A["mysql.Query(params4DB)"]
后来业务迅猛发展,入驻的商家日益增多,后台的请求变量大了;各个商家创建的商品越来越多,寻找商品效率太低了,MySQL的like查询,一次就能把数据库整死。此时你的团队判断,需要借助ES的模糊查询才能更好的支持了。于是需要在仓库层
提供一个es的Search方法,并在业务层
把之前的查询方法替换一波,之前MySQL查询所需要的参数、返回值的类型,也需要相应修改:
graph LR
biz --> A["es.Search(params4ES)"]
业务没变,却要业务层进行修改,凭啥呢?
高层和低层
软件系统就是为业务服务的,其中的业务逻辑就是一个软件系统的核心价值,而这些重要的业务逻辑都应该收敛在业务层
。相对地,接口层
只负责将这些能力暴露给外部,仓库层
只负责支撑业务数据的读写,选择使用MySQL还是ES,只是一种实现细节,不会改变产品形态。
所以这里的业务层
妥妥的就属于高层,地位最高,是系统的核心,接口层
和仓库层
属于底层,地位较低,为业务层服务,是系统的实现细节。
现在我们有了高层和低层的概念了,按依赖倒置的做法,上面的例子就可以改进为:
graph LR
biz -.调用.-> A["repository.Search(params4Search)"]
repository_impl -.实现.-> A
mysql --> repository_impl
es --> repository_impl
业务层:biz
、repository.Search(params4Search)
仓库层:repository_impl
、mysql
、es
其中repository.Search(params4Search)
是抽象接口,Search方法的参数和返回值格式,也都是业务层
定义的,仓库层
按照抽象接口的定义去实现。此时依赖关系就变为:高层没有依赖低层,低层依赖了高层定义的抽象接口。
日后支持高并发,需要借助Redis,就直接在仓库层
修改就行了,业务层完全不用做修改的喔。
细节应该依赖抽象
抽象不应该依赖细节,细节应该依赖抽象
对于后半句,我觉得只说出了一种指引或是实现方式。但我们更应该知道的是为什么要这样。
能够被抽象出来的模式,总是经过反复的推导、修改、验证的,能够通用、稳定地表达某一种业务形态的。只要业务符合该模式,就可以一直复用。
细节则是指具体,一个具体的对象和另一个具体的对象,是有区别的,不可复用的。一般来说,也是多变(经常做修改)的。
所以后半句话的重点应当是:一个稳定(少变)的模块,不应该依赖一个不稳定(多变)的模块。 类似于木桶效应。系统是一个整体,可修改性总会取决于最不可修改的一个模块(针对开发时,非运行时)。
而依赖倒置,才是实现手段。当你发现:核心模块依赖了一些具体实现,具体实现有改动时,连带着核心模块也要做相应修改,这时候可以通过依赖导致的方式,去让核心模块依赖一层抽象定义,让其变得更稳定。
总结
依赖这个词在现实生活的含义是:依靠别人或事物而不能自立或自给
。A是依赖B的,A就失去了主动,B对于A来说不可或缺,B有变动A就需要跟随。
依赖倒置原则的思想是,确定好高层和底层,让底层去依赖高层(定义的抽象接口),而不是反过来。