前言php
关于这个话题, 网上有不少文章,这里, 我但愿经过最简单的话语与你们分享.
依赖注入和控制反转两个概念让不少初学这迷惑, 以为玄之又玄,高深莫测.
这里想先说明两点:java
这里经过一个简单的案例来讲明.
在公司里有一个常见的案例: "把任务指派个程序员完成".android
把这个案例用面向对象(OO)的方式来设计,一般在面向对象设计中,名词皆可设计为对象
这句话里"任务","程序员"是名词,因此咱们考虑建立两个Class: Task 和 Phper (php 程序员)程序员
文件: 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 codeapp
咱们看一看这个设计有什么问题?
若是只是为了完成某个临时的任务,程序即写即仍,这没有问题,只要完成任务便可.
可是若是同事仰慕你的设计,要重用你的代码.你把程序打成一个类库(jar包)发给同事.
如今问题来了,同事发现这个Task 类 和 程序员 zhang3 绑定在一块儿,他全部建立的Task,都是程序员zhang3负责,他要把一些任务指派给Lee4, 就须要修改Task的源程序, 若是没有Task的源程序,就没法把任务指派给他人. 而一般类库(jar包)的使用者一般不须要也不该该来修改类库的源码,若是你们都来修改类库的源码,类库就失去了重用的设计初衷.框架
咱们很天然的想到,应该让用户来指派任务负责人. 因而有了新的设计.ide
文件: 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(); } }
这样用户就可在使用时指派特定的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来完成,公司招了写Javaer (java程序员),如今问题来了,这个Task类库的的使用者发现,任务只能指派给Phper,
一个很天然的需求就是Task应该便可指派给Phper也可指派给Javaer.
咱们发现无论Phper 仍是 Javaer 都是Coder(程序员), 把Task类对Phper类的依赖改成对Coder 的依赖便可.
这个Coder能够设计为父类或接口,Phper 或 Javaer 经过继承父类或实现接口 达到归为一类的目的.
选择父类仍是接口,主要看Coder里是否有不少共用的逻辑代码,若是是,就选择父类
不然就选接口.
这里咱们选择接口的办法:
package demo; public interface Coder { public void writeCode(); }
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"); } }
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"); } }
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(); } }
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 源码, 提升了类库的可扩展性.
回顾一下,咱们开发的Task类,
在Step1 中与Task与特定实例绑定(zhang3 Phper)
在Step2 中与Task与特定类型绑定(Phper)
在Step3 中与Task与特定接口绑定(Coder)
虽然都是绑定, 从Step1,Step2 到 Step3 灵活性可扩展性是依次提升的.
Step1 做为反面教材不可取, 至因而否须要从Step2 提高为Step3, 要看具体状况.
若是依赖的类型是惟一的Step2 就能够, 若是选项不少就选Step3设计.
依赖注入(DI)实现了控制反转(IoC)的思想.
看看怎么反转的?
Step1 程序
this.owner = new Phper("zhang3");
Step1 设计中 任务Task 依赖负责人owner, 就主动新建一个Phper 赋值给owner,
这里是新建,也多是在容器中获取一个现成的Phper,新建仍是获取,可有可无,关键是赋值, 主动赋值. 这里提一个赋值权的概念.
在Step2 和 Step3, Task 的 owner 是被动赋值的.谁来赋值,Task本身不关心,多是类库的用户,也多是框架或容器.
Task交出赋值权, 从主动赋值到被动赋值, 这就是控制反转.
什么是控制反转 ?
简单的说从主动变被动就是控制反转.
上文以依赖注入的例子,对控制反转作了个简单的解释.
控制反转是一个很普遍的概念, 依赖注入是控制反转的一个例子,但控制反转的例子还不少,甚至与软件开发无关.
这有点相似二八定律,人们老是用具体的实例解释二八定律,具体的实例不等与二八定律(不了解二八定律的朋友,请轻松忽略这个类比)
如今从其余方面谈一谈控制反转.
传统的程序开发,人们老是从main 函数开始,调用各类各样的库来完成一个程序.
这样的开发,开发者控制着整个运行过程.
而如今人们使用框架(Framework)开发,使用框架时,框架控制着整个运行过程.
对比如下的两个简单程序:
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(); } }
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 名称就不能修改.
由于,后者使用了框架,享受框架带来福利的同时,就要遵循框架的规则.
这就是控制反转.
能够说, 控制反转是全部框架最基本的特征.
也是框架和普通类库最大的不一样点.
不少Android开发工程师在享用控制反转带来的便利,去不知什么是控制反转.
就有点像深海里的鱼不知到什么是海水同样.
经过框架能够把许多共用的逻辑放到框架里,让用户专一本身程序的逻辑.
这也是为何如今,不管手机开发,网页开发,仍是桌面程序, 也不论是Java,PHP,仍是Python框架无处不在.
回顾下以前的文件: MyFramework.java
package demo; public class MyFramework { public static void main(String[] args) { Task t = new Task("Task #1"); Coder owner = new Phper("lee4"); t.setOwner(owner); t.start(); } }
这只是简单的测试程序,取名为MyFramework, 是由于它拥有框架3个最基本特征
这里建立了两个对象,实际框架可能会建立数千个对象,可能经过工厂类而不是直接建立,
这里直接装配对象,实际框架可能用XML 文件描述要建立的对象和装配逻辑.
固然实际的框架还有不少这里没涉及的内容,只是但愿经过这个简单的例子,你们对框架有个初步认识.
控制反转还有一个漂亮的比喻:
好莱坞原则(Hollywood principle)
"不要打电话给咱们,咱们会打给你(若是合适)" ("don't call us, we'll call you." )
这是好莱坞电影公司对面试者常见的答复.
事实上,不仅电影行业,基本上全部公司人力资源部对面试者都这样说.
让面试者从主动联系转换为被动等待.
为了增长本文的趣味性,这里在举个比喻讲述控制反转.
人们谈恋爱,在之前一般是男追女,如今时代进步了,女追男也很常见.
这也是控制反转
体会下你追女孩和女孩追你的区别:
你追女孩时,你是主动的,你是标准制定者, 要求身高多少,颜值多少,知足你的标准,你才去追,追谁,何时追, 你说了算.
这就相似,框架制定接口规范,对实现了接口的类调用.
等女孩追你时,你是被动的,她是标准制定者,要求有车,有房等,你买车,买房,努力工做挣钱,是为了达到标准(既实现接口规范), 你万事具有, 处于候追状态, 但时谁来追你,何时追,你不知道.
这就是主动和被动的区别,也是为何男的偏好主动的缘由.
这里模仿好莱坞原则,提一个中国帅哥原则:"不要追哥, 哥来追你(若是合适)",
简称CGP.( Chinese gentleman principle: "don't court me, I will court you")
面向对象的设计思想
第一节 提到在面向对象设计中,名词皆对象,这里作些补充.
当面对一个项目,作系统设计时,第一个问题就是,系统里要设计哪些类?
最简单的办法就是,把要设计系统的名词提出来,一般,名词可设计为对象,
可是否全部名词都须要设计对应的类呢? 要具体问题具体分析.不是不能够,是否有必要.
有时候须要把一些动词名词化, 看看现实生活中, 写做是动词,全部写做的人叫什么? 没有合适的称呼,咱们就叫做者, 阅读是动词,阅读的人就称读者. 中文经过加"者","手"使动词名词化,舞者,歌手,投手,射手皆是这类.
英语世界也相似,经过er, or等后缀使动词名词化, 如singer,writer,reader,actor, visitor.
现实生活这样, Java世界也同样.
Java经过able,or后缀使动词名词化.如Runnable,Serializable,Parcelable Comparator,Iterator.
Runnable便可以运行的东西(类) ,其余相似.
了解了动词名词化,对java里的不少类就容易理解了.
相关术语(行话)解释
Java 里术语满天飞, 让初学者望而生畏. 若是你不想让不少术语影响学习,这一节可忽视.
了解了原理,叫什么并不重要. 了解些术语的好处是便于沟通和阅读外文资料,还有就是让人看起来很专业的样子.
Coder owner = new Phper("lee4");
Coder owner = new Phper("lee4");
Phper p = (Phper) owner;
但愿上述内容, 对你们有所帮助, 谢谢.
快才助手, 在电脑上操做手机, Android屏幕同步软件
本文做者手工打造,热情推荐,网址: http://www.kwaicai.com