控制反转与依赖注入
控制反转是Spring框架的核心思想,也是因为Spring的关系这个模式为大众所知晓。
依赖抽象
先看一个比较经典的例子,代码和示意图如下
public class Application{
pivate DiskWriter writer = new DiskWriter();
public void save(){
writer.saveToDisk();
}
}
应用程序在需要存储时,直接执行了saveToDisk(),导致了高层应用程序直接依赖低层模块的API 。假设应用程序需要移植到其他平台上,而平台使用的存储介质是SSD,则应用程序无法直接重用,必须要修改代码才可以。这里由于低层的模块存储介质发生了变化,造成了高层模块也必须跟着变化,这不是一个好的设计方式。在设计上希望模块都依赖于模块的抽象,这样才能够重用高层的应用程序。
public interface IDeviceWriter{
void saveToDevice();
}
public class DiskWriter implements IDeviceWriter{
public void saveToDevice(){
System.out.println("save to disk")
}
}
public class SsdWriter implements IDeviceWriter{
public void saveToDevice(){
System.out.println("save to ssd")
}
}
public class Application{
private IDeviceWriter deviceWriter;
public void setDeviceWriter(IDeviceWriter deviceWriter){
this.deviceWriter = deviceWriter;
}
public void save(){
if(deviceWriter == null){
throw new RuntimeException("deviceWriter needed ...");
}
deviceWriter.saveToDevice();
}
}
如上述代码把Application与具体的DeviceWriter实现解耦,在存储介质变化的时候并不需要重新修改Application的实现。
应用程序不依赖于实现,但是应用程序与实现都要依赖于接口。这也正是控制反转所要表述的内容。
控制反转
IoC(Inversion of Control )的Control是控制的意思,其背后是一种依赖关系的转移。如果A依赖于B,其意义即B拥有控制权。如果转移这种关系(依赖关系的反转即控制关系的反转),将控制权由实现的一份转移至抽象的一方,让抽象方拥有控制权,可以获得组件的可重用性。IoC的实现方式有2种 1. Service Locator 2. Dependency Injection
服务定位器
服务定位器模式的目的是按需返回服务实例,将服务使用者与具体类分离。
它的实现包含了以下组件
- Client – 服务的使用者. 它在需要使用到服务的时候从服务定位器获取服务,并调用。
- Service Locator – 服务定位器从服务工程创建并注册服务到缓存。
- Service Cache – 用来存储 Service Locator 已经获取过的Service。
- Initial Context – 负责创建服务,是一个service 工厂的角色。当Service Locator在Cache中没有找到service的时候则调用它进行服务的创建。
- Service – 服务抽象或者服务的实现
核心的逻辑是 Client 需要服务的具体实现的时候调用 Service Locator 来进行创建,使Client与ServiceImpl解耦。
依赖注入
依赖注入一般是由一个依赖注入容器来负责类的实例化,在实例化的过程中把该类所依赖的抽象的实现给创建好并注入到该类的实例。注入的方式可以总结为三种
- 构造函数注入 - 实例化的过程中将依赖传入,进行对象的实例化
- Setter函数注入 - 实例化后容器将依赖通过setter函数设置进去
- 实现接口注入 - 实例化后, 容器对其中实现了某个接口的类对象进行该接口的调用。 最典型的是Spring的各种Aware接口
这个模式因为Spring的普及而被广泛使用。
对比
Service Locator模式虽然是解耦了应用和依赖的关系,却引入了Service Locator,应用本身依赖了Service Locator。
依赖注入模式,其中的类是没有任何跟自己的实现无关的内容,可以进行单独的测试和使用。对于依赖的实现,它既不知道,也不在乎它们来自何方,有容器给它们注入。就算你换了一个依赖注入容器也是不需要去更改相关类的代码。
延伸
SOLID编程原则里面的 D代表Dependency Inversion Principle 即依赖倒置原则,说的就是控制反转。而控制反转的实现即是Service Locator和Dependency Injection两种模式。
致敬一下经典。
https://martinfowler.com/articles/injection.html
https://java-design-patterns.com/patterns/service-locator/
https://java-design-patterns.com/patterns/dependency-injection/
https://book.douban.com/subject/1830509/
- Go语言实践:从新手入门到上线真实的小型服务所遇到的那些坑
- 4个简单的数据管理技巧
- MongoDB的设计模式策略
- 如何用容器实现生产级Redis sharding集群一键交付
- Hadoop旧mapreduce的map任务切分原理
- 解读Neo4j全新的Python驱动程序
- MySQL 5.7 X Plugin:流水线技术vs.并行查询技术
- 在下函数式编程,有何贵干?
- 基于Keras/Python的深度学习模型Dropout正则项
- 揭秘深度强化学习
- Swoole-2.0.1-Alpha 已发布,提供PHP原生协程支持
- Google核心技术之——PageRank算法scala实现
- 代码审查拯救世界?
- Kafka实战:从RDBMS到Hadoop,七步实现实时传输
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Python 基础(六):列表与元组
- Word 批量转 PDF
- Python 数据分析(一):NumPy 基础知识
- 每日一题 | 环形排列问题
- 每日一题 | QQ群撩妹问题
- 每日一题 | 老板出的下棋问题
- Qt音视频开发12-mpv解码播放
- 算法专题 | 10行代码实现的最短路算法——Bellman-ford与SPFA
- 每日一题 | 土豪割草问题
- 高阶面试:伯努利过程
- Python 分析电影《南方车站的聚会》
- Python 基础(十八):命名空间 & 作用域
- Python爬虫(三):BeautifulSoup库
- Python 爬虫(四):Selenium 框架
- Python 爬虫(五):PyQuery 框架