python ioc di_轻松理解 Java开发中的依赖注入(DI)和控制反转(IOC)_weixin_39652760的博客-程序员宅基地

技术标签: python ioc di  

关于这个话题, 网上有很多文章,这里, 我希望通过最简单的话语与大家分享.

依赖注入和控制反转两个概念让很多初学这迷惑, 觉得玄之又玄,高深莫测.

这里想先说明两点:

依赖注入和控制反转不是高级的,很初级,也很简单.

在JAVA世界,这两个概念像空气一样无所不在,彻底理解很有必要.

第一节 依赖注入 Dependency injection

这里通过一个简单的案例来说明.

在公司里有一个常见的案例: "把任务指派个程序员完成".

把这个案例用面向对象(OO)的方式来设计,通常在面向对象设计中,名词皆可设计为对象

这句话里"任务","程序员"是名词,所以我们考虑创建两个Class: Task 和 Phper (php 程序员)

Step1 设计

文件: 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

我们看一看这个设计有什么问题?

如果只是为了完成某个临时的任务,程序即写即仍,这没有问题,只要完成任务即可.

但是如果同事仰慕你的设计,要重用你的代码.你把程序打成一个类库(jar包)发给同事.

现在问题来了,同事发现这个Task 类 和 程序员 zhang3 绑定在一起,他所有创建的Task,都是程序员zhang3负责,他要把一些任务指派给Lee4, 就需要修改Task的源程序, 如果没有Task的源程序,就无法把任务指派给他人. 而通常类库(jar包)的使用者通常不需要也不应该来修改类库的源码,如果大家都来修改类库的源码,类库就失去了重用的设计初衷.

我们很自然的想到,应该让用户来指派任务负责人. 于是有了新的设计.

Step2 设计:

文件: 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.

Step3 设计

我们发现不管Phper 还是 Javaer 都是Coder(程序员), 把Task类对Phper类的依赖改为对Coder 的依赖即可.

这个Coder可以设计为父类或接口,Phper 或 Javaer 通过继承父类或实现接口 达到归为一类的目的.

选择父类还是接口,主要看Coder里是否有很多共用的逻辑代码,如果是,就选择父类

否则就选接口.

这里我们选择接口的办法:

新增Coder接口,

文件: Coder.java

package demo;

public interface Coder {

public void writeCode();

}

修改Phper类实现Coder接口

文件: Phper.php

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");

}

}

新类Javaer实现Coder接口

文件: Javaer.php

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 源码, 提高了类库的可扩展性.

回顾一下,我们开发的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交出赋值权, 从主动赋值到被动赋值, 这就是控制反转.

第二节 控制反转 Inversion of control

什么是控制反转 ?

简单的说从主动变被动就是控制反转.

上文以依赖注入的例子,对控制反转做了个简单的解释.

控制反转是一个很广泛的概念, 依赖注入是控制反转的一个例子,但控制反转的例子还很多,甚至与软件开发无关.

这有点类似二八定律,人们总是用具体的实例解释二八定律,具体的实例不等与二八定律(不了解二八定律的朋友,请轻松忽略这个类比)

现在从其他方面谈一谈控制反转.

传统的程序开发,人们总是从main 函数开始,调用各种各样的库来完成一个程序.

这样的开发,开发者控制着整个运行过程.

而现在人们使用框架(Framework)开发,使用框架时,框架控制着整个运行过程.

对比以下的两个简单程序:

简单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();

}

}

简单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 名称就不能修改.

因为,后者使用了框架,享受框架带来福利的同时,就要遵循框架的规则.

这就是控制反转.

可以说, 控制反转是所有框架最基本的特征.

也是框架和普通类库最大的不同点.

很多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个最基本特征

main函数,即程序入口.

创建对象.

装配对象.(setOwner)

这里创建了两个对象,实际框架可能会创建数千个对象,可能通过工厂类而不是直接创建,

这里直接装配对象,实际框架可能用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 里术语满天飞, 让初学者望而生畏. 如果你不想让很多术语影响学习,这一节可忽视.

了解了原理,叫什么并不重要. 了解些术语的好处是便于沟通和阅读外文资料,还有就是让人看起来很专业的样子.

耦合(couple): 相互绑定就是耦合第一节 Step1,Step2,Step3 都是.

紧耦合(Tight coupling) Step1 中,Task 和 zhang3 绑在一起; Step2中 Task 和 Phper 绑在一起, 都是.

松耦合(Loose coupling) Step3 中,Task 和 Coder 接口绑在一起就是

解耦(Decoupling): 从Step1 , Step2, 到 Step3 的设计就是Decoupling, 让对象可以灵活组合.

上溯造型或称向上转型(Upcasting). 把一个对像赋值给自己的接口或父类变量就是.因为画类图时接口或父类在画在上面,所以是Upcasting. Step3中一下程序就是:

Coder owner = new Phper("lee4");

下溯造型或称向下转型(Downcasting). 和Upcasting 相反,把Upcasting过后的对象转型为之前的对象. 这个上述程序不涉及,顺带说一下

Coder owner = new Phper("lee4");

Phper p = (Phper) owner;

注入(Inject): 通过方法或构造函数把一个对象传递给另一个对象. Step3 中的setOwner 就是.

装配(Assemble): 和上述注入是一个意思,看个人喜好使用.

工厂(Factory): 如果一个类或对象专门负责创建(new) 对象,这个类或对象就是工厂

容器(Container): 专门负责存放创建好的对象的东西. 可以是个Hash表或 数组.

面向接口编程(Interface based programming) Step3 的设计就是.

希望上述内容, 对大家有所帮助, 谢谢.

进一段广告

快才助手, 在电脑上操作手机, Android屏幕同步软件

本文作者手工打造,热情推荐,网址: http://www.kwaicai.com

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_39652760/article/details/111065018

智能推荐

confluence安装_大数据技术之Hive第2章 Hive安装与启动_weixin_39731807的博客-程序员宅基地

本章思维导图:2.1 Hive安装地址1)Hive官网地址http://hive.apache.org/2)文档查看地址https://cwiki.apache.org/confluence/display/Hive/GettingStarted3)下载地址http://archive.apache.org/dist/hive/4)github地址https://github.com/a...

springMVC中的@ModelAttribute注解_h_triump的博客-程序员宅基地

springMVC小总结常用注解总结以下内容是我个人对@ModelAttribute注解的见解,如有错误请批评指正!!!例子中用到的代码:代码获取@ModelAttribute注解1.作用:出现在方法上:表示当前方法会在控制器方法执行前先执行出现在参数上:获取指定的数据给参数赋值2.应用场景:当提交表单数据不是完整的实体数据时,保证没有提交的字段使用数据库原来的数据。这句话的理解:比如说数据库中有一个user表,表中有三个字段(name,age,birthday),并且有一条n

Android开发过程中可能遇到的问题_System_err的博客-程序员宅基地

一、导入项目时,提示 “invalid project description”无效的项目描述,应该与ADT版本不同有关。出错背景:外来源码放在工作区,然后导入Eclipse。解决方法:外来源码先放在非工作区(如:桌面),然后导入到Eclipse,导入页面勾上 “copy projects to into workspace” 复制一份到工作区。二、打开

Django报错UnicodeEncodeError: 'ascii' codec can't encode characters 之解决方法 _jiangnanandi的博客-程序员宅基地

 原先用Python 2.4 + Django 0.95 写得程序,当我把环境升级到Python 2.5 +  Django 0.97(geodjango)的时候,并且把两个表做了外键关链的情况下,发现后台添加数据,会出现错误,Python报错如下: [color=#FF0000]UnicodeEncodeError: ascii codec cant encode characters i

Redis用法及实现原理_薰衣草的味道的博客-程序员宅基地

虽然项目中一直在使用Redis,但是在实际的工作中,对Redis的使用还是仅仅停留在分布式缓存上,并且也仅仅只是会set和get,对于Redis的一些高级用法以及Redis为什么高性能模棱两可,所以最近也一直在钻研Redis,学习了Redis很多不为人知的东西。所以在这里记录一下,希望自己能够坚持把所有的内容完善。Redis用法集分布式缓存缓存一致性问题同步双删解决方案订阅binlog,同步数据解决方案缓存穿透空对象布隆过滤器缓存击穿互斥锁永不过期缓存雪崩

IE各个版本的调试工具_keyler的博客-程序员宅基地

最近使用JQuery开发的网页在IE6浏览器下出现样式不对,VS2010调试的时候有些不便。无意之中看到由微软开发的modern.ie,继而发现BrowserStack,发现有很多的IE调试工具,就用微软开发的那款吧。Expression Web SuperPreview 4.0软件简介目前有很多集成了IE各各版本的调试工具。如IETester、Multiple IE、IE

随便推点

Python3数据分析——NumPy快速入门教程(官网教程翻译)_Asia-Lee的博客-程序员宅基地

目录一、基础篇1、创建数组2、打印数组3、基本运算4、通用函数(ufunc)5、索引,切片和迭代二、形状操作1、更改数组的形状2、组合(stack)不同的数组3、将一个数组分割(split)成几个小数组三、复制和视图1、完全不拷贝2、视图(view)和浅复制3、深复制四、函数和方法(method)总览五、进阶1、广播法则(rule...

ruby array_Ruby中带有示例的Array.rotate()方法_cumt30111的博客-程序员宅基地

ruby array Ruby Array.rotate()方法 (Ruby Array.rotate() Method)In this article, we will study about Array.rotate() method. You all must be thinking the method must be doing something related to rotatin...

Android 内存优化-命令行定位内存泄露,Monitors及Profiler追寻内存问题_Alex-panda的博客-程序员宅基地_android 临时变量

一、如何定位内存泄漏1、Android Studio通过命令行 打开AndroidStudio,选中Monitors选项卡,这个时候选中Memory,会有一个可视化的视图。打开AndroidStudio,选中Terminal选项卡,运行项目到真机上,进入主界面操作各个界面,然后重新进入主界面,这个时候项目中应该只有一个MainActivity的实例,此时在Terminal中输入adb shell dumpsys meminfo 进程名 -d,就有目前栈中所有的Activ...

2018年长沙理工大学第十三届程序设计竞赛_di6499的博客-程序员宅基地

链接:https://www.nowcoder.com/acm/contest/96#question持续更新ing,请多多关注~A.LL题目描述:“LL是什么?这都不知道的话,别说自己是程序猿啊!” “longlong?” “。。。肯定是LoveLive啊!” qwb为了检验你是否是真正的程序猿,决定出道题考考你:现在程序会输入一行字符串,如果恰好是lo...

oracle数据库将汉字转拼音函数_菜鸟小杰子的博客-程序员宅基地_oracle汉字转拼音函数

--调用方式: SELECT f_getSpell('江西') from dual; --返回 'jx'--SELECT f_getSpell('江西',0) from dual; --返回 'jiangxi'create or replace type spell_code as object(spell varchar2(10),code number);create or replace type t_spellcode is table of spell_code;--返回拼音与代

PSTN 与 PBX 业务_piperzero的博客-程序员宅基地

在继续学习 FreeSWITCH 之前,我们有必要了解一下传统的电话网所能提供的服务。这些服务有的是你已经熟悉的,有的也可能没听说过。有一些业务在 VoIP 中实现起来就异常简单,而有一些业务已经不需要了。PSTN 业务POTS除为用户提供基本的话音通话外,PSTN 还能提供一些附加的业务,这些业务在国外称为普通老式电话业务(POTS,Plain Old Telephone Servi...

推荐文章

热门文章

相关标签