编程范式巡礼第三季 谈谈依赖反转

编程范式巡礼第三季 谈谈依赖反转

今天会进入深一点的主题,谈一个软件开发的"道":依赖反转。根据我的观察,这也是架构师与程序员的分水岭之一。

什么是依赖反转

引出问题

让我们从Uncle Bob和小明的一段对话开始。原文地址:如何成为一名优秀的架构师

小明:我要领导一个团队,还要做所有关于数据库、框架和Web服务器的重要决定。
Uncle Bob:好吧,如果是这样,你就没必要成为一名软件架构师了。
小明:当然有必要了!我要成为一个能够做所有重要决定的人。
Uncle Bob:这样很好,只是你没有列出哪些才是重要的决定。你刚才说的那些跟重要的决定没有什么关系。

Bob大叔一上来就抛出了一个反常识的观点,工具的选择决策并不是重要的问题,为什么这么认为?

Uncle Bob:你认为业务逻辑依赖数据库,但实际上不是这样的。如果你的架构足够好,最起码业务逻辑不应该依赖数据库。
小明:如果业务逻辑对数据库一无所知,它怎么使用这些工具呢?
Uncle Bob:依赖反转。你要让数据库依赖业务逻辑,而不是让业务逻辑依赖数据库。

Bob大叔认为重要的是业务逻辑的实现,工具只是服务于业务逻辑。但小明提出的是一个非常实际的问题,业务逻辑是基于工具来实现的,就好比画家是依赖画具创作的,东方和西方由于画具不同,展现的艺术作品就完全不同。这个时候Bob大叔抛出了这次的重头戏:依赖反转。

小明:那就更加费解了!既然上层策略(假设你指的是业务逻辑)要调用下层策略(假设你指的是数据库),那么就应该是上层策略依赖下层策略,就像调用者依赖被调用者一样。这是众所周知的!
Uncle Bob:在运行时确实是这样的,但在编译时我们要把依赖反转过来。上层策略的代码不要引用任何下层策略的代码。

小明的说法有道理,适用于我们现实生活常识,但是计算机领域恰恰是有自己独特规则的,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大叔用一段咏叹调结束了这一次对话,提出了他的核心看法:架构设计要能适应未来的变化。

  1. 在技术层面,最大的挑战来自于无法预测的性能容量增长,需要不断与更先进的工具进行接轨。
  2. 在业务层面,响应要求日益严峻,代码的修改成本(主要由耦合带来)会成为重要的生产力指标。
    解决思路是模块与工具解耦、模块与模块解耦,依赖反转无疑是实现解耦有力方法,是架构师的有力工具。

依赖反转思想的扩展

依赖反转不仅仅是一个模式或者方法,更重要的是其体现的解耦思想,下面再介绍两个具有同样思想的重要范式。

切面范式

切面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;
    }

泛型范式与日常工作非常接近,我们每时每刻都能接触到,是除了基本范式之外最为古老的了。编写泛型代码一定会用到抽象思维,这不是一种常识思维,但可以作为一种练习,是由低到高的修炼捷径。

小结

最后想说的是,最重要的不是依赖反转这个方法,而是依赖反转的思想。练成了这种思想,小处讲,可以节省代码、提高效率。大处讲,可以适应变化、应对未来。这里只是抛个砖,请有志于架构设计的同学务必深入掌握。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容