依赖注入——人生若只如初见
2016-06-13 22:07
549 查看
刚接触一门学问或技术,就像刚认识一位可以当做潜在对象的异性,幸福又美好。
当接触的久了,了解的多了,习以为常了,就没有了当初的兴奋和快乐。
正所谓,人生若只如初见。
初识依赖注入,甚是美好,解决我在开发过程中的诸多疑惑,相见恨晚。但愿人长久。
假设一种情景,类/接口(以下统称类) Client 要在其 greet() 方法中使用类 Service,所以 Client 实例里面会有一个 Service 实例的引用,而且要将该引用指向一个 Service 实例(这不废话吗?!)。
如何在 Client 实例中得到 Service 实例?实现方法很灵活(额,我讨厌灵活,越是灵活我越是不知所措),我们列举一下,不外乎有 4 种方式:
Client 中构造 Service 实例
在 Client 构造方法参数中传入 Service 实例
从 setter 方法中传入 Service 实例
通过接口方法传入 Service 实例
下面一一实现。
so easy,信手拈来。就是在 Client 的构造函数中 new 一个ConcreteService 实例而已。
也不难,就是将在 Client 外面构造好的 Service 实例通过 Client 的构造方法的参数传进去而已。
在 Client 中写一个 setter 方法,通过该方法将 Service 实例传进去。
Client 实现 ServiceSetter 接口,实现 setService() 方法,通过该方法将 Service 实例传给 Client 实例。
好了,4 中实现方法枚举完了。请各位看官半个小板凳,我们要开讲重头戏了。
在这个例子中,Service 是被使用者,是依赖(Dependency);Client 是使用者。
将 Service 实例从 Client 的构造方法或 setter 方法中传进去的过程叫做注入(Injection)。注意此处的注入是名词,而非动词。
而负责传递过程的构造方法或 setter 方法称为注入者(Injector)。
简单来说,就是被使用者在使用者外面构造好,然后传递给使用者。所以,除了第1种实现方法,其余3种都是依赖注入。第1种方法直接在使用者内部构造依赖,不属于依赖注入。
在 Client 构造方法参数中传入 Service 实例
从 setter 方法中传入 Service 实例
通过接口方法传入 Service 实例
那么,它们各自有什么优劣呢?
而且更为糟糕的是,当多个依赖之间有相关性的时候,我们必须保证多个依赖的 setter 方法的调用顺序不会影响实际效果,这将大大增加代码维护的难度。
高层次的模块不应该依赖低层次的模块,它们都应该依赖抽象;
抽象不应有依赖具体实现,具体实现应该依赖抽象;
从对象创建和引用小议解耦
当接触的久了,了解的多了,习以为常了,就没有了当初的兴奋和快乐。
正所谓,人生若只如初见。
初识依赖注入,甚是美好,解决我在开发过程中的诸多疑惑,相见恨晚。但愿人长久。
啥是依赖注入
俗话说的好,“Talk is cheap. Show me the code.”翻译过来就是:空谈误国,代码兴邦。咱们不空谈理论,先看一个例子。假设一种情景,类/接口(以下统称类) Client 要在其 greet() 方法中使用类 Service,所以 Client 实例里面会有一个 Service 实例的引用,而且要将该引用指向一个 Service 实例(这不废话吗?!)。
如何在 Client 实例中得到 Service 实例?实现方法很灵活(额,我讨厌灵活,越是灵活我越是不知所措),我们列举一下,不外乎有 4 种方式:
Client 中构造 Service 实例
在 Client 构造方法参数中传入 Service 实例
从 setter 方法中传入 Service 实例
通过接口方法传入 Service 实例
下面一一实现。
在 Client 中构造
public class Client { Service mService; public Client() { mService = new ConcreteService(); } public String greet() { return "Hello " + mService.getName(); } }
so easy,信手拈来。就是在 Client 的构造函数中 new 一个ConcreteService 实例而已。
在 Client 构造方法中传入
public class Client { Service mService; public Client(Service service) { mService = service; } public String greet() { return "Hello " + mService.getName(); } }
也不难,就是将在 Client 外面构造好的 Service 实例通过 Client 的构造方法的参数传进去而已。
setter 方法传入
public class Client { Service mService; public void setService(Service service) { mService = service; } // Set the other service to be used by this client public void setOtherService(Service otherService) { if (otherService == null) { throw new InvalidParameterException("otherService must not be null"); } this.otherService = otherService; } public String greet() { return "Hello " + mService.getName(); } }
在 Client 中写一个 setter 方法,通过该方法将 Service 实例传进去。
通过接口的方法传入
public interface ServiceSetter { void setService(Service servcie); } public class Client implements ServiceSetter { Service mService; @Override public void setService(Service service) { mService = service; } public String greet() { return "Hello " + mService.getName(); } }
Client 实现 ServiceSetter 接口,实现 setService() 方法,通过该方法将 Service 实例传给 Client 实例。
好了,4 中实现方法枚举完了。请各位看官半个小板凳,我们要开讲重头戏了。
在这个例子中,Service 是被使用者,是依赖(Dependency);Client 是使用者。
将 Service 实例从 Client 的构造方法或 setter 方法中传进去的过程叫做注入(Injection)。注意此处的注入是名词,而非动词。
而负责传递过程的构造方法或 setter 方法称为注入者(Injector)。
简单来说,就是被使用者在使用者外面构造好,然后传递给使用者。所以,除了第1种实现方法,其余3种都是依赖注入。第1种方法直接在使用者内部构造依赖,不属于依赖注入。
常见类型比较
如上,依赖注入共有3种类型:在 Client 构造方法参数中传入 Service 实例
从 setter 方法中传入 Service 实例
通过接口方法传入 Service 实例
那么,它们各自有什么优劣呢?
在 Client 构造方法中传入
优势
依赖一次性的被传进来,保证了 Client 使用依赖的时候所有依赖都是可用的,不用关心其是否为空。劣势
不够灵活,必须要在 Client 被构造之前构造好依赖,而实际情况中依赖可能后于 Client 被构造好,而且可能会根据具体情况(例如主客态)在不同的时候对同一个 Client 设置不同的依赖。从 setter 方法中传入 Service 实例
优势
足够灵活,可以在适当的时候设置合适的依赖。劣势
由于调用 setter 方法的时机未知,所以在使用依赖的时候,必须花费额外的精力去保证所有依赖都是可用的,依赖不能在调用 setter 方法之前被使用。// Set the service to be used by this client public void setService(Service service) { this.service = service; } // Set the other service to be used by this client public void setOtherService(Service otherService) { this.otherService = otherService; } // Check the service references of this client private void validateState() { if (service == null) { throw new IllegalStateException("service must not be null"); } if (otherService == null) { throw new IllegalStateException("otherService must not be null"); } } // Method that uses the service references public void doSomething() { validateState(); service.doYourThing(); otherService.doYourThing(); }
而且更为糟糕的是,当多个依赖之间有相关性的时候,我们必须保证多个依赖的 setter 方法的调用顺序不会影响实际效果,这将大大增加代码维护的难度。
通过接口方法传入 Service 实例
优势
该方法可以不用关心 Client 的实现细节,只需遵守接口的方法规则,将依赖(名词)本身的引用通过 setter 设置到 Client 中去,即可达到同样的效果。public abstract class Service {
public abstract String getName();
}
public interface ServiceSetter { void setService(Service servcie); } public class Client implements ServiceSetter { Service mService; @Override public void setService(Service service) { mService = service; } public String greet() { return "Hello " + mService.getName(); } }
public class ConcreteService extends Service {
@Override
public String getName() {
// TODO Auto-generated method stub
return null;
}
public void setServiceSetter(ServiceSetter s) {
s.setService(this);
}
}
public class Injector {
public static void main(String... args) {
ConcreteService service = new ConcreteService();
Client c = new Client();
service.setServiceSetter(c);
}
}
劣势
控制反转
控制反转,Inversion of Control,是面向对象编程中的一种设计原则,目的是降低代码之间的耦合。常见的方式有依赖注入(Dependency Injection)和依赖查找(Dependency lookup)。依赖倒置
Dependency Inversion Principle,具体为:高层次的模块不应该依赖低层次的模块,它们都应该依赖抽象;
抽象不应有依赖具体实现,具体实现应该依赖抽象;
Dagger
更多资料
Dependency injection从对象创建和引用小议解耦
相关文章推荐
- NopCommerce架构分析(一)Autofac依赖注入类生成容器
- JavaScript中的依赖注入详解
- 浅析依赖注入框架Autofac的使用
- 浅析Node.js中使用依赖注入的相关问题及解决方法
- 理解php依赖注入和控制反转
- 如何用js 实现依赖注入的思想,后端框架思想搬到前端来
- Javascript技术栈中的四种依赖注入详解
- 深入理解Javascript里的依赖注入
- Javascript技术栈中的四种依赖注入小结
- AngularJS学习笔记之依赖注入详解
- 详解Angularjs中的依赖注入
- AngularJs动态加载模块和依赖注入详解
- 简化你的 Java 对象依赖关系
- Spring的@Autowired实践感知
- 理解php依赖注入和控制反转
- Android学习之 使用依赖注入函数库Roboguice
- 控制反转与依赖注入
- Spring的意义
- Java反射及依赖注入简单模拟
- Spring3核心技术之DI依赖注入