一, 依赖注入 Dependency injection
这里通过一个日常常见的案例说明:
"把任务指派给程序员".
把这个案例用面向对象的方式来设计, 通常在面向对象的设计中, 名词可以设计为对象; 这句话中 "任务","程序员" 都是名词, 则我们创建两个 Class:Task 和 Phper .
Step 1 设计
Phper.java
- package demo;
- public class Phper {
- private String name;
- public Phper(String name){
- this.name=name;
- }
- public void writeCode(){
- System.out.println(this.name + "is writing php code");
- }
- }
Task.java
- package demo;
- public class Task {
- private String name;
- private Phper owner;
- public Task(String name){
- this.name =name;
- this.owner = new Phper("zhang3");
- }
- public void start(){
- System.out.println(this.name+ "started");
- this.owner.writeCode();
- }
- }
MyFramework.java 这是个简单的测试程序
- package demo;
- public class MyFramework {
- public static void main(String[] args) {
- Task t = new Task("Task #1");
- t.start();
- }
- }
运行结果:
- Task #1 started
- hang3 is writing PHP code
上面这样写会有个问题, Task 类和程序员 zhang3 绑定在一起, 所有的任务都会交给 zhang3 进行处理, 如果想要交给其他人处理, 则需要修改 Task 代码, 显然不太符合我们的初衷. 于是我们继续设计.
Step 2 设计
Phper.java 不变
Task.java
- package demo;
- public class Task {
- private String name;
- private Phper owner;
- public Task(String name){
- this.name =name;
- }
- public void setOwner(Phper owner){
- this.owner = owner;
- }
- public void start(){
- System.out.println(this.name+ "started");
- this.owner.writeCode();
- }
- }
MyFramework.java, 这是个简单的测试程序.
- package demo;
- public class MyFramework {
- public static void main(String[] args) {
- Task t = new Task("Task #1");
- Phper owner = new Phper("lee4");
- t.setOwner(owner);
- t.start();
- }
- }
这样用户在使用时就可以指派特定的 Phper 了.
我们知道, 任务依赖程序员, Task 类依赖 Phper 类, 之前, Task 类绑定特定的实例, 现在这种依赖可以在使用时按需绑定, 这就是依赖注入 (DI).
这个例子, 我们通过方法 setOwner 注入依赖对象. 另外, 我们也可以在 Task 的构造函数中进行注入.
在 Java 开发中, 把一个对象实例传给一个新建对象的情况十分普遍, 通常这就是注入依赖.
Step2 的设计实现了依赖注入.
我们来看看 Step2 的设计有什么问题.
如果公司是一个单纯使用 PHP 的公司, 所有开发任务都有 Phper 来完成, 这样这个设就已经很好了, 不用优化.
但是随着公司的发展, 有些任务需要 JAVA 来完成, 公司招了写 Javaer (java 程序员), 现在问题来了, 这个 Task 类库的的使用者发现, 任务只能指派给 Phper,
一个很自然的需求就是 Task 应该即可指派给 Phper 也可指派给 Javaer.
Step 3 设计
我们发现不管 Phper 还是 Javaer 都是 Coder(程序员), 把 Task 类对 Phper 类的依赖改为对 Coder 的依赖即可.
这个 Coder 可以设计为父类或接口, Phper 或 Javaer 通过继承父类或实现接口 达到归为一类的目的.
选择父类还是接口, 主要看 Coder 里是否有很多共用的逻辑代码, 如果是, 就选择父类
否则就选接口.
这里我们选择接口的办法:
1. 新增 Coder 接口,
Coder.java
- package demo;
- public interface Coder {
- public void writeCode();
- }
2. 修改 Phper 类实现 Coder 接口
Phper.java
- package demo;
- public class Phper implements Coder {
- private String name;
- public Phper(String name){
- this.name=name;
- }
- public void writeCode(){
- System.out.println(this.name + "is writing php code");
- }
- }
3. 新增 Javaer 实现 Coder 接口
Javaer.java
- package demo;
- public class Javaer implements Coder {
- private String name;
- public Javaer(String name){
- this.name=name;
- }
- public void writeCode(){
- System.out.println(this.name + "is writing java code");
- }
- }
修改 Task 由对 Phper 类的依赖改为对 Coder 的依赖
Task.java
- package demo;
- public class Task {
- private String name;
- private Coder owner;
- public Task(String name){
- this.name =name;
- }
- public void setOwner(Coder owner){
- this.owner = owner;
- }
- public void start(){
- System.out.println(this.name+ "started");
- this.owner.writeCode();
- }
- }
修改用于测试的类使用 Coder 接口:
- package demo;
- public class MyFramework {
- public static void main(String[] args) {
- Task t = new Task("Task #1");
- // Phper, Javaer 都是 Coder, 可以赋值
- Coder owner = new Phper("lee4");
- //Coder owner = new Javaer("Wang5");
- t.setOwner(owner);
- t.start();
- }
- }
现在用户可以和方便的把任务指派给 Javaer 了, 如果有新的 Pythoner 加入, 没问题.
类库的使用者只需让 Pythoner 实现 (implements) 了 Coder 接口, 就可把任务指派给 Pythoner, 无需修改 Task 源码, 提高了类库的可扩展性.
Step1 设计中任务 Task 依赖负责人 owner, 就主动新建一个 Phper 赋值给 owner,
这里是新建, 也可能是在容器中获取一个现成的 Phper, 新建还是获取, 无关紧要, 关键是赋值, 主动赋值. 这里提一个赋值权的概念.
在 Step2 和 Step3, Task 的 owner 是被动赋值的. 谁来赋值, Task 自己不关心, 可能是类库的用户, 也可能是框架或容器.
Task 交出赋值权, 从主动赋值到被动赋值, 这就是控制反转.
二, 控制反转 Inversion of control
简单的说从主动变被动就是控制反转.
对比以下的两个简单程序:
1. 简单 java 程序
- package demo;
- public class Activity {
- public Activity(){
- this.onCreate();
- }
- public void onCreate(){
- System.out.println("onCreate called");
- }
- public void sayHi(){
- System.out.println("Hello world!");
- }
- public static void main(String[] args) {
- Activity a = new Activity();
- a.sayHi();
- }
- }
2. 简单 Android 程序
- package demo;
- import Android.App.Activity;
- import Android.os.Bundle;
- import Android.widget.TextView;
- public class MainActivity extends Activity
- {
- @Override
- public void onCreate(Bundle savedInstanceState)
- {
- super.onCreate(savedInstanceState);
- TextView tv = new TextView(this);
- tv.append("Hello");
- tv.append("world!");
- setContentView(tv);
- }
- }
这两个程序最大的区别就是, 前者程序的运行完全由开发控制, 后者程序的运行由 Android 框架控制.
两个程序都有个 onCreate 方法.
前者程序中, 如果开发者觉得 onCreate 名称不合适, 想改为 Init, 没问题, 直接就可以改, 相比下, 后者的 onCreate 名称就不能修改.
因为, 后者使用了框架, 享受框架带来福利的同时, 就要遵循框架的规则.
这就是控制反转.
可以说, 控制反转是所有框架最基本的特征.
也是框架和普通类库最大的不同点.
参照文章:
https://www.jianshu.com/p/506dcd94d4f9
来源: http://www.jianshu.com/p/e25ed2fb1c99