小课堂week18 编程范式巡礼第三季 谈谈依赖反转
编程范式巡礼第三季--谈谈依赖反转
今天会进入深一点的主题,谈一个软件开发的"道":依赖反转。根据我的观察,这也是架构师与程序员的分水岭之一。
什么是依赖反转
引出问题
让我们从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大叔用一段咏叹调结束了这一次对话,提出了他的核心看法:架构设计要能适应未来的变化。
- 在技术层面,最大的挑战来自于无法预测的性能容量增长,需要不断与更先进的工具进行接轨。
- 在业务层面,响应要求日益严峻,代码的修改成本(主要由耦合带来)会成为重要的生产力指标。
解决思路是模块与工具解耦、模块与模块解耦,依赖反转无疑是实现解耦有力方法,是架构师的有力工具。
依赖反转思想的扩展
依赖反转不仅仅是一个模式或者方法,更重要的是其体现的解耦思想,下面再介绍两个具有同样思想的重要范式。
切面范式
切面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;
}
泛型范式与日常工作非常接近,我们每时每刻都能接触到,是除了基本范式之外最为古老的了。编写泛型代码一定会用到抽象思维,这不是一种常识思维,但可以作为一种练习,是由低到高的修炼捷径。
小结
最后想说的是,最重要的不是依赖反转这个方法,而是依赖反转的思想。练成了这种思想,小处讲,可以节省代码、提高效率。大处讲,可以适应变化、应对未来。这里只是抛个砖,请有志于架构设计的同学务必深入掌握。
小课堂week18 编程范式巡礼第三季 谈谈依赖反转的更多相关文章
- 小课堂week19 编程范式巡礼最终季 超级范式
编程范式巡礼(最终季)--超级范式 本周是编程范式系列的最后一次分享,让我们拉长视角,看向远方,进入"元编程"的领域,在<冒号课堂>中起了个很酷的名字:"超级 ...
- 小课堂week16 编程范式巡礼第一季 三大基石
编程范式巡礼第一季 三大基石 最近迷上了一些哲史类书籍,回望过去.放眼未来,往往沉浸在其思维之美中无法自拔.计算机编程是一门非常年轻的学科,沉淀不足也是年轻的一个侧面,在编程领域,有足够思想深度的作品 ...
- 小课堂week17 编程范式巡礼第二季 并发那些事
编程范式巡礼第二季 并发那些事 继续上周的编程范式话题,今天想聊一下并发范式. 并发也算一种范式? 真正的并发式编程,绝不只是调用线程API或使用synchronized.lock之类的关键字那么简单 ...
- 冒号课堂 编程范式与OOP思想
上篇:编程范式与编程语言 第1课 开班导言 第2课 重要范式 第3课 常用范式 第4课 重温范式 第5课 语言小谈 第6课 语言简评 下篇:抽象机制与对象范式 第7课 抽象封装 第8课 抽象接口 第9 ...
- [小北De编程手记] : Lesson 05 玩转 xUnit.Net 之 从Assert谈UT框架实践
这一篇,本文会介绍一下基本的断言概念,但重点会放在企业级单元测试的相关功能上面.下面来跟大家分享一下xUnit.Net的断言,主要涉及到以下内容: 关于断言的概念 xUnit.Net常用的断言 关于单 ...
- Spark小课堂Week3 FirstSparkApp(Dataframe开发)
Spark小课堂Week3 FirstSparkApp(代码优化) RDD代码简化 对于昨天练习的代码,我们可以从几个方面来简化: 使用fluent风格写法,可以减少对于中间变量的定义. 使用lamb ...
- Spark小课堂Week3 FirstSparkApp(RDD开发)
Spark小课堂Week3 FirstSparkApp 问题:Java有哪些数据结构 大致有如下几种,其中List与Map是最重要的: List Map Set Array Heap Stack Qu ...
- 编程范式:命令式编程(Imperative)、声明式编程(Declarative)和函数式编程(Functional)
主要的编程范式有三种:命令式编程,声明式编程和函数式编程. 命令式编程: 命令式编程的主要思想是关注计算机执行的步骤,即一步一步告诉计算机先做什么再做什么. 比如:如果你想在一个数字集合 collec ...
- Python3学习之路~6.1 编程范式:面向过程 VS 面向对象
编程范式 编程是程序员用特定的语法+数据结构+算法组成的代码来告诉计算机如何执行任务的过程,一个程序是程序员为了得到一个任务结果而编写的一组指令的集合,正所谓条条大路通罗马,实现一个任务的方式有很多种 ...
随机推荐
- Django Suit v2-dev 使用
转:链接:https://www.jianshu.com/p/84fa8219fb48 官方文档: 链接 Git: 链接 install Django Suit 为了适配 Django 有许多不同的版 ...
- perl相关知识
转:http://www.runoob.com/perl/perl-cgi-programming.html Perl 是 Practical Extraction and Report Langua ...
- Mock(模拟后端接口数据)配合Vuex的使用
1.下载Mock cnpm install Mockjs -S 2.新建一个data.js存放新生成的mock文件 编辑mock 并导出 const Mock = require('mockjs' ...
- Kuhn-Munkres算法
KM算法——二分图最大权匹配 我们前面学过了二分图匹配的匈牙利算法.但这种算法是针对没有权值的图来说的. 肯定有人想问,没有权值的用匈牙利算法,哪有权值的图要求最大权或最小权匹配呢?? 这里就引出了我 ...
- POJ 2406 Power Strings 简单KMP模板 strcmp
http://poj.org/problem?id=2406 只是模板,但是有趣的是一个strcmp的字符串比较函数,学习到了... https://baike.baidu.com/item/strc ...
- HDU 6136 Death Podracing(循环链表)
[题目链接] http://acm.hdu.edu.cn/showproblem.php?pid=6136 [题目大意] 一堆人在操场上跑步,他们都有一定的速度和初始位置, 当两个人相遇的时候编号较小 ...
- 什么是ClassLoader
ClassLoader 做什么的? 顾名思义,它是用来加载 Class 的.它负责将 Class 的字节码形式转换成内存形式的 Class 对象.字节码可以来自于磁盘文件 *.class,也可以是 j ...
- Unity JsonFx 插件使用
在Unity中使用 JsonFx 插件笔记(提示:以下在 Unity3D v5.4.0 版本 Win 平台下测试成功) 下载 JsonFx 插件注意:JsonFx 插件其实就是一个 dll 文件(如果 ...
- 通过wifi上网,桥接模式下virtualBox虚拟机无法连上网的解决办法
https://jingyan.baidu.com/article/948f59242e601dd80ff5f929.html
- Scala 元组
与列表一样,元组也是不可变的,但与列表不同的是元组可以包含不同类型的元素. 元组的值是通过将单个的值包含在圆括号中构成的.例如: val t = (1, 3.14, "Fred") ...