今天会进入深一点的主题,谈一个软件开发的"道":依赖反转。根据个人观察,这也是架构师与程序员的分水岭之一。java
让咱们从Uncle Bob和小明的一段对话开始。原文地址:如何成为一名优秀的架构师程序员
小明:我要领导一个团队,还要作全部关于数据库、框架和Web服务器的重要决定。
Uncle Bob:好吧,若是是这样,你就不必成为一名软件架构师了。
小明:固然有必要了!我要成为一个可以作全部重要决定的人。
Uncle Bob:这样很好,只是你没有列出哪些才是重要的决定。你刚才说的那些跟重要的决定没有什么关系。算法
Bob大叔一上来就抛出了一个反常识的观点,工具的选择决策并非重要的问题,为何这么认为?spring
Uncle Bob:你认为业务逻辑依赖数据库,但实际上不是这样的。若是你的架构足够好,最起码业务逻辑不该该依赖数据库。
小明:若是业务逻辑对数据库一无所知,它怎么使用这些工具呢?
Uncle Bob:依赖反转。你要让数据库依赖业务逻辑,而不是让业务逻辑依赖数据库。数据库
Bob大叔认为重要的是业务逻辑的实现,工具只是服务于业务逻辑。但小明提出的是一个很是实际的问题,业务逻辑是基于工具来实现的,就比如画家是依赖画具创做的,东方和西方因为画具不一样,展示的艺术做品就彻底不一样。这个时候Bob大叔抛出了此次的重头戏:依赖反转。编程
小明:那就更加费解了!既然上层策略(假设你指的是业务逻辑)要调用下层策略(假设你指的是数据库),那么就应该是上层策略依赖下层策略,就像调用者依赖被调用者同样。这是众所周知的!
Uncle Bob:在运行时确实是这样的,但在编译时咱们要把依赖反转过来。上层策略的代码不要引用任何下层策略的代码。json
小明的说法有道理,适用于咱们现实生活常识,可是计算机领域偏偏是有本身独特规则的,Bob大叔指出了这一点。咱们编写的计算机代码并非直接运行的,当中会通过编译器的处理,而这个中间处理,让依赖反转变成了可能。通俗点说,在计算机世界中,工具是能够晚于业务逻辑出现的。安全
下面来看代码的例子:服务器
小明:在Java里,发送者最起码要知道接收者的基本类型。
Uncle Bob:是的。不过,不论是哪种状况,发送者都不知道接收者具体的类型。数据结构
发送者(业务逻辑):BusinessRule
基本类型:BusinessRuleGateway
具体类型(工具):MySqlBusinessRuleGateway
package businessRules; import entities.Something; public class BusinessRule { private BusinessRuleGateway gateway; public BusinessRule(BusinessRuleGateway gateway) { this.gateway = gateway; } public void execute(String id) { gateway.startTransaction(); Something thing = gateway.getSomething(id); thing.makeChanges(); gateway.saveSomething(thing); gateway.endTransaction(); } }
import entities.Something; public interface BusinessRuleGateway { Something getSomething(String id); void startTransaction(); void saveSomething(Something thing); void endTransaction(); }
package database; import businessRules.BusinessRuleGateway; import entities.Something; public class MySqlBusinessRuleGateway implements BusinessRuleGateway { public Something getSomething(String id) { // 从MySQL里读取一些数据 } public void startTransaction() { // 开始一个事务 } public void saveSomething(Something thing) { // 把数据保存到MySQL } public void endTransaction() { // 结束事务 } }
能够看到,业务逻辑BusinessRule是在运行时对工具MySqlBusinessRuleGateway进行调用的,但在编译时,二者并无依赖关系。
一切都是那么的完美,依赖确实反转了,可是存在一个问题,就是多出了一个东西:基本类型BusinessRuleGateway。
小明:这样的话,若是业务逻辑须要全部的工具,那么你必须把全部工具都放到Gateway接口里。
小明:这样的话,你就会有不少接口,并且有不少实现类。
小明:这样子很浪费时间!我为何要这样作呢?这样只会增长更多的代码。
Uncle Bob:这个叫做接口分离原则。每一个业务逻辑只使用一部分数据库工具,因此每一个业务逻辑只定义可以知足须要的接口。
小明提出了一个开发中很实际的问题,基本类型是多余的代码,会增长工做。Bob大叔则以为,基本类型能够认为是对工具的需求,也是须要思考的部分。
小明:但首先要先决定使用什么数据库、Web服务器或框架啊!
Uncle Bob:不,实际上应该在开发后期才开始作这些事情——在你掌握了更多信息以后。
哀哉,当架构师草率地决定要使用一个数据库,后来却发现使用文件系统效率更高。
哀哉,当架构师草率地决定使用一个Web服务器,后来却发现团队须要的不过是一个Socket接口。
哀哉,当架构师草率地决定使用一个框架,后来却发现框架提供的功能是团队不须要的,反而给团队带来了诸多约束。
幸哉,当架构师在掌握了足够多的信息后才决定该用什么数据库、Web服务器或框架。幸哉,当架构师为团队鉴别出运行缓慢、耗费资源的IO设备和框架,这样他们就能够构建飞速运行的轻量级测试环境。
幸哉,当架构师把注意力放在那些真正重要的事情上,并把那些不重要的事情放在一边。
Bob大叔用一段咏叹调结束了这一次对话,提出了他的核心见解:架构设计要能适应将来的变化。
依赖反转不只仅是一个模式或者方法,更重要的是其体现的解耦思想,下面再介绍两个具备一样思想的重要范式。
切面Aspect是与程序的纵向主流执行方向横向正交的关注焦点。此类代码以片段的形式散落在各处,虽具备类似的逻辑,却没法用传统的方法提炼成模块。典型的例子如:日志输出、代码跟踪、性能监控、异常处理、安全检查、事务处理等。为解决此类问题,AOP应运而生。它将每类横切关注点封装到单独的Aspect模块中,经过定义执行点和代码绑定起来。
AOP从描述来看是比较抽象的,简单来讲就是除了上面提到的模块和工具、模块和模块之外,在模块内代码片段之间也存在必定的依赖关系,而AOP是对代码片段解耦的方法。
下面是AOP代码片段(进行日志跟踪)。
@Around("execution(* spring.services.MyDemoService3.*(..))") public void traceBusiness(ProceedingJoinPoint jp) throws Throwable { System.out.println("before enter method "+jp.getSignature().getName()); jp.proceed(jp.getArgs()); System.out.println("after enter method "+jp.getSignature().getName()); }
有人会问了,为啥没有业务逻辑。这是由于,AOP与业务逻辑是能够无关的,业务逻辑角度有可能没法感知。可是信息不透明容易造成恶性的发展,因此实际使用时要运用annotation进行限制,避免过分使用。
泛型编程是算法导向的,即以算法为起点和中心点,逐渐将其所涉及的概念内涵模糊化、外延扩大化,将其所涉及的运算抽象化、通常化,从而扩展算法的适用范围。
泛型又是解耦思想在具体算法实现中的运用。算法在运行时是包含数据结构和算法逻辑两个部分的,这个时候咱们能够用基本类型来替代具体的数据结构,实现二者的解耦。
下面是代码例子(将一组Json字符转换为对象),转换的目标对象与算法逻辑是无关的,因此用基本类型T来进行了替代。
static <T> List<T> convertJsonToPojo(List<String> jsonStrings, Class<T> c, boolean generateSeq) { List<T> result = new ArrayList<>(); ObjectMapper objectMapper = ObjectMapperFactory.create(); jsonStrings.forEach(map -> { result.add(convertJsonToPojo(map, c, generateSeq, objectMapper)); }); return result; }
泛型范式与平常工做很是接近,咱们每时每刻都能接触到,是除了基本范式以外最为古老的了。编写泛型代码必定会用到抽象思惟,这不是一种常识思惟,但能够做为一种练习,是由低到高的修炼捷径。
最后想说的是,最重要的不是依赖反转这个方法,而是依赖反转的思想。练成了这种思想,小处讲,能够节省代码、提升效率。大处讲,能够适应变化、应对将来。这里只是抛个砖,请有志于架构设计的同窗务必深刻掌握。