控制反转是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 来进行创建,使Client与ServiceImpl解耦。
依赖注入一般是由一个依赖注入容器来负责类的实例化,在实例化的过程中把该类所依赖的抽象的实现给创建好并注入到该类的实例。注入的方式可以总结为三种
这个模式因为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/