系列(8)Java 中的依赖注入 (DI) 和控制反转 (IOC)

一、依赖注入 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");
    }
}
  1. 修改 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();
    }
}
  1. 修改用于测试的类使用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

    原文作者:kevenZheng
    原文地址: https://www.jianshu.com/p/e25ed2fb1c99
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞