Markdown版本笔记 | 个人GitHub首页 | 个人博客 | 个人微信 | 个人邮箱 |
---|---|---|---|---|
MyAndroidBlogs | baiqiantao | baiqiantao | bqt20094 | baiqiantao@sina.com |
依赖注入 DI 控制反转 IOCphp
Inversion Of Controljava
什么是控制反转 ?
简单的说,从主动变被动就是控制反转
。git
控制反转是一个很普遍的概念, 依赖注入是控制反转的一个例子,但控制反转的例子还不少,甚至与软件开发无关。程序员
传统的程序开发,人们老是从 main 函数开始,调用各类各样的库来完成一个程序。这样的开发,开发者控制着整个运行过程
。而如今人们使用框架(Framework)开发,使用框架时,框架控制着整个运行过程
。github
对比如下的两个简单程序。面试
一、简单java程序微信
public class Activity { public Activity() { this.onCreate();//------------开发者主动调用onCreate()方法------------ } public void onCreate() { System.out.println("onCreate called"); } public static void main(String[] args) { Activity a = new Activity(); } }
二、简单Android程序框架
public class MainActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { //------------框架自主调用onCreate()方法------------ super.onCreate(savedInstanceState); System.out.println("onCreate called"); } }
这两个程序最大的区别就是,前者程序的运行彻底由开发控制,后者程序的运行由Android框架控制
。ide
虽然两个程序都有个onCreate方法,但在前者程序中,若是开发者以为onCreate名称不合适,想改成Init,没问题,直接就能够改; 相比下,后者的onCreate名称就不能修改,由于后者使用了框架,享受框架带来福利的同时,就要遵循框架的规则。函数
这就是控制反转。
能够说,控制反转是全部框架最基本的特征,也是框架和普通类库最大的不一样点
。
控制反转还有一个漂亮的比喻,好莱坞原则(Hollywood principle):
don't call us, we'll call you。
不要打电话给咱们,咱们会打给你(若是合适)
这是好莱坞电影公司对面试者常见的答复。
事实上,不仅电影行业,基本上全部公司人力资源部对面试者都会说相似的话,让面试者从主动联系转换为被动等待。
这里经过一个简单的案例来讲明。在公司里有一个常见的案例:把任务指派个程序员完成。
把这个案例用面向对象的方式来设计,咱们建立两个类:Task 和 Phper (php 程序员)
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"); } }
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(); } }
测试:
public class MyFramework { public static void main(String[] args) { Task t = new Task("Task #1"); t.start(); } }
运行结果:
Task #1 started zhang3is writing php code
咱们看一看这个设计有什么问题。
若是同事仰慕你的设计,要重用你的代码,你把程序打成一个类库(jar包)发给同事。如今问题来了:同事发现这个Task 类 和 程序员 zhang3 绑定在一块儿,他全部建立的Task,都是程序员zhang3负责,他要把一些任务指派给Lee4, 就须要修改Task的源程序, 若是没有Task的源程序,就没法把任务指派给他人。
而类库(jar包)的使用者一般不须要也不该该来修改类库的源码,咱们很天然的想到,应该让用户来指派任务负责人,因而有了新的设计。
Phper不变。
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(); } }
测试:
public class MyFramework { public static void main(String[] args) { Task t = new Task("Task #1"); Phper owner = new Phper("lee4"); t.setOwner(owner);//用户在使用时按需指派特定的PHP程序员 t.start(); } }
这样用户就可在使用时指派特定的PHP程序员。
咱们知道,Task类依赖Phper类,以前,Task类绑定特定的实例,如今这种依赖能够在使用时按需绑定,这就是依赖注入(DI)。
这个例子中,咱们经过方法setOwner
注入依赖对象,另外一个常见的注入方式是在Task的构造函数中注入:
public Task(String name,Phper owner){ this.name = name; this.owner = owner; }
在Java开发中,把一个对象实例传给一个新建对象的状况十分广泛,一般这就是注入依赖,Step2 的设计实现了依赖注入。
咱们来看看Step2 的设计有什么问题。
若是公司是一个单纯使用PHP的公司,全部开发任务都有Phper 来完成,这样这个设就已经很好了,不用优化。可是随着公司的发展,有些任务须要JAVA来完成,公司招了Java程序员,如今问题来了,这个Task类库的的使用者发现,任务只能指派给Phper,一个很天然的需求就是,Task应该便可指派给Phper也可指派给Javaer。
咱们发现无论Phper 仍是 Javaer 都是Coder(程序员), 把Task类对Phper
类的依赖改成对Coder
的依赖便可。
新增Coder接口
public interface Coder { void writeCode(); }
修改Phper类实现Coder接口
public class Phper implements Coder { private String name; public Phper(String name){ this.name=name; } @Override public void writeCode(){ System.out.println(this.name + " is writing php code"); } }
新类Javaer实现Coder接口
public class Javaer implements Coder { private String name; public Javaer(String name){ this.name=name; } @Override public void writeCode(){ System.out.println(this.name + " is writing Java code"); } }
修改Task由对Phper类的依赖改成对Coder的依赖
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(); } }
测试
public class MyFramework { public static void main(String[] args) { Task t = new Task("Task #1"); Coder owner = new Phper("lee4"); //Coder owner = new Javaer("Wang5"); t.setOwner(owner); t.start(); } }
如今用户能够和方便的把任务指派给 Javaer 了,若是有新的 Pythoner 加入,没问题,类库的使用者只需让 Pythoner 实现 Coder 接口,就可把任务指派给 Pythoner, 无需修改 Task 源码, 提升了类库的可扩展性。
回顾一下,咱们开发的Task类:
实例
绑定(zhang3 Phper)类型
绑定(Phper)接口绑
定(Coder)虽然都是绑定, 从Step1,Step2 到 Step3 灵活性、可扩展性是依次提升的。
Step1 做为反面教材不可取, 至因而否须要从 Step2 提高为 Step3, 要看具体状况。
依赖注入(DI)实现了控制反转(IoC)的思想,看看怎么反转的:
新建
一个 Phper 赋值给 owner(这里多是新建,也多是在容器中获取一个现成的 Phper,是新建仍是获取可有可无,关键是主动赋值
)。被动赋值
的,谁来赋值,Task 本身不关心,多是类库的用户,也多是框架或容器,Task交出赋值权
,从主动赋值到被动赋值
,这就是控制反转。public class ClassA { ClassB classB; public void setClassB(ClassB b) { classB = b; } }
public class ClassA { ClassB classB; public void ClassA(ClassB b) { classB = b; } }
public class ClassA { @inject ClassB classB;//此时并不会完成注入,还须要依赖注入框架的支持,如RoboGuice,Dagger2 //... public ClassA() {} }
接口
interface InterfaceB { void doIt(); }
形式一
class A implements InjectB { ClassB classB; @Override public void injectB(ClassB b) { classB = b; } }
形式二
public class ClassA { InterfaceB clzB; public void doSomething() { clzB = (InterfaceB) Class.forName("...").newInstance();//根据预先在配置文件中设定的实现类的类名动态加载实现类 clzB.doIt(); } }
此种接口注入方式由于具有侵入性,它要求组件必须与特定的接口相关联,所以实际使用有限。
2019-5-12