看了几个高赞答案,感觉说得还是太啰嗦了。依赖注入听起来好像很复杂,但是实际上炒鸡简单,一句话说就是:
本来我接受各种参数来构造一个对象,现在只接受一个参数——已经实例化的对象。
也就是说我对对象的『依赖』是注入进来的,而和它的构造方式解耦了。构造它这个『控制』操作也交给了第三方,也就是控制反转。
不举抽象的什么造汽车或者小明玩儿手机的例子了。一个很实际的例子,比如我们要用 redis 实现一个远程列表。耦合成一坨的代码可以是这样写,其中我们需要自己构造需要用的组件:
class RedisList: def __init__(self, host, port, password): self._client = redis.Redis(host, port, password) def push(self, key, val): self._client.lpush(key, val) l = RedisList(host, port, password)
依赖翻转之后是这样的:
class RedisList: def __init__(self, redis_client) self._client = redis_client def push(self, key, val): self._client.lpush(key, val) redis_client = get_redis_client(…) l = RedisList(redis_client)
看起来好像也没什么区别,但是考虑下面这些因素:
- 线下线上环境可能不一样,get_redis_client 函数在线上可能要做不少操作来读取到对应的配置,可能并不是不是一个简单的函数。
- redis 这个类是一个基础组件,可能好多类都需要用到,每个类都去自己实例化吗?如果需要修改的话,每个类都要改。
- 我们想依赖的是 redis 的 lpush 方法,而不是他的构造函数。
所以把 redis 这个类的实例化由一个单一的函数来做,而其他函数只调用对应的接口是有意义的。
就这么简单啊。。
====================================
依赖注入(DI)和控制反转(IOC)基本是一个意思,因为说起来谁都离不开谁。
简单来说,a依赖b,但a不控制b的创建和销毁,仅使用b,那么b的控制权交给a之外处理,这叫控制反转(IOC),而a要依赖b,必然要使用b的instance,那么
- 通过a的接口,把b传入;
- 通过a的构造,把b传入;
- 通过设置a的属性,把b传入;
这个过程叫依赖注入(DI)。
那么什么是IOC Container?
随着DI的频繁使用,要实现IOC,会有很多重复代码,甚至随着技术的发展,有更多新的实现方法和方案,那么有人就把这些实现IOC的代码打包成组件或框架,来避免人们重复造轮子。
所以实现IOC的组件或者框架,我们可以叫它IOC Container。
===============================
只讲原理,不讲过程。
大多数面向对象编程语言,在调用一个类的时候,先要实例化这个类,生成一个对象。
如果你在写一个类,过程中要调用到很多其它类,甚至这里的其它类,也要“依赖”于更多其它的类,那么可以想象,你要进行多少次实例化。
这就是“依赖”的意思。
依赖注入,全称是“依赖注入到容器”, 容器(IOC容器)是一个设计模式,它也是个对象,你把某个类(不管有多少依赖关系)放入这个容器中,可以“解析”出这个类的实例。
所以依赖注入就是把有依赖关系的类放入容器(IOC容器)中,然后解析出这个类的实例。仅此而已。
如果你看的是PHP,可以看看我的博客:
作者:Angry Bugs
链接:https://www.zhihu.com/question/32108444/answer/581948457
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
===========================
如何用最简单的方式解释:
____________________________________________________________
还是一步一步解释最为简单,好比搬用数学公式,不理解还是不懂。
____________________________________________________________
假如有一个 船(Chuan)类 ,成员变量中肯定需要一个 桨(Jiang) 类,
class Chuan{ Jiang j = new Jiang() ; }
如果船要干什么事,肯定需要桨的参与。所以是十分 “依赖”桨;
来新需求,桨需要统一长度为10米。需要重构:这时候我们需要控制桨的长度为10在构造方法中。我们需要这么写;
class Chuan{ Jiang j = new Jiang(10) ; }
来了,这一个改动同时需要修改桨的类即加入长度属性,又需要修改船其中的new Jiang()代码传入长度(修改两处)。这时候就设计者就思考,为什么我们加入一个特性需要更改两个类中代码(这也就是耦合度高)!
所以我们要解耦要依赖注入;
常用解耦方式:
- 构造方法注入
如下:我重构代码的时候再也不用管依赖的桨长还是短,宽颜色都不管!因为船构造方法依赖了桨。任你桨怎么设计,我用的时候传一个桨进来即可。(下层依赖上层,用的时候传入,而不是针对下层去修改)
class Chuan{ Jiang j ; public Chuan(Jiang j){ this.j = j; }; }
- 工厂模式注入
新需求如下:桨的生产商觉得不能让你全国各地的船用之前都需要制造一个桨,天津船需要时候天津当地new 一个桨,北京需要 北京当地new一个桨,造成了使用时多处代码依赖桨,这样之后再修改桨的规范就要各处去修改,太麻烦了且容易错误不统一。
所以搞一个全国工厂,让桨的生产过程即new的过程就在这一个类中,船就只关注Factory类中方法便可。(核心业务逻辑需要依赖的类,该类实例化交给第三方类来实现注入。)
工厂模式 新增个Factory 工厂类去注入; 工厂类如下
class Factory { /** * 通过msg来确定你要什么长度颜色大小。工厂出一套规范。之后约束拓展在此类就可以进行 **/ public Jiang getJiang(String msg){ if(msg=”10″){ return new Jiang(10) }else if(msg=”red”){ return new Jiang(“red”) }; }; }
船的代码变为
class Chuan { Jiang j ; void run(){ Factory h = new Factory(); j=h.getJiang(“red“); //得到了红色的桨 j=h.getJiang(“10“); //得到长度10的桨 }; }
- 框架注入(本质还是工厂设计模式的具体实现)
本质也是第三方依赖注入,但是这个第三方可以脱离类。将对象依赖映射信息存储在容器一般为.xml 或者特定的对象中,并实现动态的注入。需要用的时候直接拿就是的。
最后本人个人理解:
为什么要有依赖注入(一种设计代码模式),因为我们要控制反转(设计代码的思路)。为什么控制反转。因为我们软件设计需要符合软件设计原则依赖倒置(设计代码原则),单一职责原则,开闭原则。
归根到底都是,寻找业务中容易变化的点,寻找解耦的点, 让其更加可控,让程序员改代码的时候容易些,同时对系统的影响小。
=======================================
作者:夏昊
链接:https://www.zhihu.com/question/32108444/answer/856898130
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
小A刚到公司老大安排了一个活,公司前不久刚开发了一个社交网站,运行不太稳定,经常会出现莫名其妙的bug,需要在必要的地方加上日志,方便找到错误,小A很快就开发好了日志记录类,为了以后的扩展性,还添加了一个接口:
public interface ILogger { void doLog(); }
public class ConsolLogger implements ILogger { @Override public void doLog() { System.out.println(“打印日志到控制台”); } }
先在添加好友功能这里增加一个日志:
public class Friend { private ILogger logger = new ConsoleLogger (); public void addFriend(){ System.out.println(“添加好友!”); logger.doLog();//添加日志 } }
发现好多地方需要添加的,一个一个加上去,三天后终于全部加好了。
这天老大又找到了小A:小A啊,现在日志是在控制台打印的,你看能不能保存在文件里面啊。小A皱了皱眉,先估算了一下工作量,需要先开发一个保存日志到文件的类,然后逐个修改,工作量有点大哦,而且都是重复性的工作,太没挑战,万一以后还需要再改那岂不是又要浪费几天美好时光,有没有简单点的办法呢?
工厂模式,小A突然灵光一闪,对就是它了,先写一个记录日志到文件的类:
public class FileLogger implements ILogger{ public void doLog(){ System.out.println(“记录日志到文件”); } }
再写一个工厂类:
public class LoggerFactory { public static ILogger createLogger(){ return new FileLogger(); } }
现在可以通过工厂创建日志对象了:
public class Friend { private FileLogger logger = LoggerFactory.createLogger(); public void addFriend(){ System.out.println(“添加好友!”); logger.doLog();//添加日志 } }
以后再有需求变动的时候只需要修改工厂类就可以了,完美,等等似乎还有一点瑕疵,如果能够实现连工厂类都不需要修改岂不是更完美,有点得寸进尺了哦,人类的智慧是无限的看步骤:
1. 将日志的实现类的全限定名放到配置文件中
<bean id = “myLogger” class=”cn.xh.logger.FileLogger”>
2. 通过xml解析根据id从配置文件中读出日志的实现类的全限定名
3. 通过反射动态创建日志实现类的对象
修改以后的工厂类伪代码如下:
public class LoggerFactory { public static ILogger createLogger(String id){ //解析xml根据id获取要创建的日志类的全限定名 //使用反射动态创建日志实现类对象 //将该对象返回 return …; } }
如果需要修改日志类,现在只需要修改xml配置文件就可以了,完美。
这就是spring ioc实现的基本原理,当然身为一个伟大的产品,怎么可以如此简单呢.
二.Spring容器
在Spring中有一个核心概念叫容器,顾名思意,容器是用来装东西的,装的什么东西呢?就是需要管理的对象和对象之间的关系,装在哪里呢?HashMap中,当然远比这要复杂。
我们用容器来实例化对象,管理对象之间的依赖。
在spring中有两个重要的接口:BeanFactory和ApplicationContext,所谓的容器就是实现了BeanFactory接口或者BeanFactory接口的类的实例,BeanFactory是最顶层最基本的接口,它描述了容器需要实现的最基本的功能,比如对象的注册,获取。
public interface BeanFactory { String FACTORY_BEAN_PREFIX = “&”; /* * 四个不同形式的getBean方法,获取实例 */ Object getBean(String name) throws BeansException; <T> T getBean(String name, Class<T> requiredType) throws BeansException; <T> T getBean(Class<T> requiredType) throws BeansException; Object getBean(String name, Object… args) throws BeansException; // 是否存在 boolean containsBean(String name); // 是否为单实例 boolean isSingleton(String name) throws NoSuchBeanDefinitionException; //是否为多例 boolean isPrototype(String name) throws NoSuchBeanDefinitionException;// // 名称、类型是否匹配 boolean isTypeMatch(String name, Class<?> targetType) throws NoSuchBeanDefinitionException; // 获取类型 Class<?> getType(String name) throws NoSuchBeanDefinitionException; // 根据实例的名字获取实例的别名 String[] getAliases(String name); }
ApplicationContext依赖BeanFactory接口,它描述的内容更加广泛,例如资源的获取等等。
当然除了这两个接口还有很多其它接口,这里不重点讨论,附上一张图以作了解。
通常我们使用的最多的容器是实现了ApplicationContext接口的类,ClassPathXmlApplicationContext和FileSystemXmlApplicationContext
ClassPathXmlApplicationContext在类路径下寻找配置文件来实例化容器,默认是读取 src 目录下的配置文件
FileSystemXmlApplicationContext在文件系统路径下寻找配置文件来实例化容器,默认是读取项目名下一级,与src同级的配置文件
这里的配置文件是xml文件,它描述了被管理的对象和对象之间的依赖(beans.xml)。
<beans xmlns=”http://www.springframework.org/schema/beans” xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance” xsi:schemaLocation=” http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd”> <bean id=”userDao” class=”cn.xh.dao.UserDaoImpl”></bean> </beans>
在这个配置文件中有一行配置:
<bean id=”userDao” class=”cn.xh.dao.UserDaoImpl”></bean>
表示使用spring容器管理的对象UserDaoImpl
加载配置文件创建容器:
ApplicationContext applicationContext = new ClassPathXmlApplicationContext(“beans.xml”);
获取容器中的对象
UserDaoImpl userDao = (UserDaoImpl) applicationContext.getBean(“userDao”);
来比较一下beanFactory和applicationContext:
1.BeanFactory接口定义了容器的最基本功能,它可以读取类的配置文档,管理类的加载,实例化,维护类之间的依赖关系。实现它的容器实例化时并不会初始化配置文件中定义的类,初始化动作发生在第一次调用时,第一次调用创建好对象后就放入缓存中以后使用直接从缓存中获取。
2. applicationContext接口除了提供容器的基本功能外还提供了很多的扩展功能,实现它的容器实例化时就会将配置文件中定义的对象初始化。
3.最常用的的容器是实现了applicationContext接口的容器ClassPathXmlApplicationContext和FileSystemXmlApplicationContext。
==============================================
为了架构师而努力!
我举个简单的例子
1.在原始社会人们想吃饭的话,需要自己找食材,然后自己用工具自己做。
2.后来社会上出现了一个叫饭店的地方,人们想吃饭只需要跑到饭店,然后给钱,饭就做好了。
3.再后来外卖出现了,你不需要去饭店,打开手机点一下,然后饭就来了。
然后分析下这三种情况
1.你需要知道如何做饭(构造方法)、找到食材(持有依赖),这个很明显耦合比较严重,因为如果食材换了,你做饭的方法也要换。
2.你不需要知道如何做饭,也不需要找食材,你只需要找到饭店(工厂模式),这个已经很明显降低了耦合,但是你需要知道饭店的位置(持有工厂的依赖)
3.你什么都不需要知道,打开手机点一下就行了。这个解耦就很明显了吧!
代码我就不写了
实现依赖注入的方式很多 比如 构造方法注入 Set方法注入 接口方式注入
====================================================
作者:CHANGEX
链接:https://www.zhihu.com/question/32108444/answer/582118313
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
在Spring中有两个很重要的概念IoC(控制反转)和DI(依赖注入),这两个功能一起使用就能实现解耦。
首先我们要明白IoC就是一个工厂模式的实现,DI的作用就是给某个对象设置属性值。
最主要的作用是让new对象这件事情做到可配置化,我们在xml里面写的的各种<bean>节点实际上就是在new对象(反射)
这么做是如何进行解耦的呢?为什么要这么做呢?
首先第一个问题,我们来看一个案例:
按照MVC架构来写代码,我们来看Dao层和Service层:
1.dao层接口TestDao
2.dao层实现类TestDaoImpl
3.service接口TestService
4.service实现类TestServiceImpl
不使用IoC和DI:
在service需要使用dao层实例:
private TestDao testDao=new TestDaoImpl();
此时,如果我们需要切换dao层实例为TestDaoImpl2该怎么做?
1.编写TestDaoImpl2的相关代码
2.修改service层代码为:
private TestDao=new TestDaoImpl2();
这里只有一个service层引用了这个dao,那如果有100个类都引用了这个dao,那么我们需要new100个dao层实现类,还需要在每次修改代码时,找到这100个service去修改其中的代码。
那如果使用IoC和DI呢?
1.在TestDaoImpl上加上@Repository注解
2.在Service层注入:
@Autowired
private TestDao testDao;
如过需要修改TestDao的实现类,我们需要:
1.编写TestDaoImpl2的具体代码
2.取消TestDaoImpl上的Repository注解
3.在TestDaoImpl2上加上Repository注解
看到没,这一次我们没有修改Service层任何代码,就算有100个类中都注入了TestDao,我们也不需要再去修改了。而且由于是单例模式,这100个service中使用的是同一个dao实例,减少了内存的使用。
至于为什么要解耦,实际上如果我们的程序在第一次上线之后如果再也不需要维护和更新的话,完全不需要考虑什么架构啊、解耦啊这些乱七八糟的概念。可现实是,程序上线之后需要不断的维护和更新,难以避免的就需要修改以前的代码,如果不进行解耦,我们在修改某一处代码时可能需要同时去修改很多很多的地方。而解耦就是为了解决这个问题而生的。
使用xml进行配置的好处是解决了硬编码的问题,如果我们需要切换实现类,只需要修改xml文件,不需要修改代码,当然也不需要重新编译代码了。
===================================
作者:知乎用户
链接:https://www.zhihu.com/question/32108444/answer/417561137
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
我认为我这博客讲的很清楚,节选一段讲DI的:
## DI & IoC
首先名词解释,DI全称是Dependency injection,依赖注入的意思。而IoC是Inversion of control 控制反转。
要了解依赖注入和控制反转,首先我们不得不提到面向对象设计中的五大设计原则:S.O.L.I.D。
#### S.O.L.I.D – 面向对象五大设计原则
SRP The Single Responsibility Principle 单一责任原则
OCP The Open Closed Principle 开放封闭原则
LSP The Liskov Substitution Principle 里氏替换原则
ISP The Interface Segregation Principle 接口分离原则
DIP The Dependency Inversion Principle 依赖倒置原则
这五种思想原则对我们平常的软件开发设计非常重要,大家可以具体去了解下。
#### 依赖倒置原则
这里我们重点讲下依赖倒置原则:实体必须依靠抽象而不是具体实现。它表示高层次的模块不应该依赖于低层次的模块,它们都应该依赖于抽象。
在传统软件设计中,我们一般都是上层代码依赖下层代码,当下层代码变动时,我们上层代码要跟着变动,维护成本比较高。这时我们可以上层定义接口,下层来实现这个接口,从而使得下层依赖于上层,降低耦合度。(PC主板和鼠标键盘接口就是一个很好的例子,各数据厂商根据主板上的接口来生产自己的鼠标键盘产品,这样鼠标坏了后我们可以随便换个符合接口要求的鼠标,而不用修改主板上的什么东西)
#### 控制反转
上面讲的依赖倒置是一种原则,而控制反转就是实现依赖倒置的一种具体方法。控制反转核心是把上层(类)所依赖单元的实例化过程交由第三方实现,而类中不允许存在对所依赖单元的实例化语句。举个例子:
“`php
class Comment
{
…
public function afterInsert()
{
$notification = new EmailNotification(…);
$notification->send(…);
}
}
“`
如上,假如我们在用户提交评论后通知被评论者,这里通知方式是邮件,而且是直接在类中实例化邮件通知类,这样代码耦合度高,如果换个短信通知方式就不得不改这里面代码,具体好的实现我们下面会讲到。
#### 依赖注入
依赖注入是一种设计模式,是一种IoC的具体实现,实现了IoC自然就符合依赖倒置原则。依赖注入的核心思想是把类中所依赖单元的实例化过程放到类外面中去实现,然后把依赖注入进来。常用的依赖注入方式有属性注入和构造函数注入。比如用构造函数注入解耦上面代码:
“`php
// 通知接口
interface Notifaction
{
public function send(…);
}
// 短信通知实现通知接口
class SmsNotification implements Notification
{
public function send(…)
{
…
}
}
// 评论类
class Comment
{
…
protected $notification;
public function __construct(Notification $smsNotification)
{
$this->notification = $smsNotification;
}
public function afterInsert()
{
$this->notification->send(…);
}
}
// 实例化短信通知类
$smsNotification = new SmsNotification(…);
// 通过构造函数方法注入
$comment = new Comment($smsNotification);
…
$comment->save();
“`
这样,我们先定义Notification接口,里面有个send方法,让后面的通知者不管是邮件类还是短信类都实现这个接口,然后在外面通过构造函数方式注入进来,这样就解决了Comment类对具体通知方法的依赖,只要是实现了Notification接口的,都可以通过构造函数传进来,Comment类完全不用做任何修改。这样无论对于代码维护还是单元测试(可以模拟实现一个Notification类),都非常方便。
依赖注入是IoC的一种具体实现,是一种解耦手段。当然IoC不止这一种实现,比如Yii中的Service Locator(服务定位器)
## IoC container/DI container
当项目比较大时,就会有许多类似上面评论类和通知类这种依赖关系,整个项目会非常复杂。这时候就需要一个集中的地方来管理这些依赖,我们把它叫IoC container 控制反转容器,它提供了动态地创建、注入依赖单元、映射依赖关系等功能。这样可以集中管理依赖关系,也减少很多代码量。
出自:Rootrl的Blog
===========================
看了所有答案,发现大多数人的答案模棱两可,甚至还有误导性,特来回答一下,首先得先说清几个名词 IoC, DI, SL, DIP。
IoC(控制反转)目前比较流行的两种方式 DI(依赖注入模式) 和 SL(服务器定位模式),DI 是遵循 DIP(依赖反转原则)的,SL 是 anti-pattern(反模式)的,不遵循 DIP。
因为注入的形式五花八门,为了代码复用性和扩展性出现了 IoC container 这么个东西,它是遵循各自 DI/SL 模式实现的容器,DI 容器会大量的运用反射机制来实现。
提到的 Laravel 中 Service Container 核心是 illuminate/container 是 DI/SL 的混合体。
其他就不多说了,关于这个概念,可以多读几遍下面这篇文章:
作者:知乎用户
链接:https://www.zhihu.com/question/32108444/answer/263667819
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
=======================================
补充一下4楼的答案:
给定一个场景,类A需要使用类B提供的某些方法,所以A依赖于B。
控制反转(IOC)关注的是对象该由“谁”来管理(包括创建和销毁),这个“谁”就是IOC容器。当容器负责管理对象的创建和销毁时,对象的控制权就不在使用这个对象的使用者手中,在上面的场景里类A需要使用类B,但是A不直接创建B,也不关心B如何被容器创建,A只管向容器要B的实例。
那么问题来了,容器创建好了B的实例后怎么交到A的手里呢?通过依赖注入(DI)
接口注入,setter注入,构造器注入这三种方式任选其一,给容器打开一扇门,容器就会穿过这扇门把B的实例交到A手中。
所以依赖注入关注的是对象实例的交付方式。IOC容器创建好了实例,DI将实例交到使用者手中。
作者:听风的海角
链接:https://www.zhihu.com/question/32108444/answer/268358237
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
=================================
额,感觉我们把dependency injection(依赖注入)想的太复杂了。就像一碗粉丝卖出了鱼翅的价格一样。
首先,依赖注入就是:向我们的程序中传入了一个已经被实例化好了的变量,这个变量内部的改变我们不需要知道,只要这个变量的接口没有变。先记住这句话,我们再来举一个例子。
来,把我们想像成一个程序应用。
今天我们(程序)要从武汉去深圳。需要几步?可以这样子:
- 我们(程序)在去哪网买机票
- 我们(程序)从滴滴叫车
- 我们(程序)坐车从家到机场
- 我们(程序)坐飞机从武汉到深圳。
如果,去哪网出问题了,我们(程序)需要换成携程网买机票;
如果,滴滴出问题了,我们(程序)需要在街上拦的士;
怎么去简化这个过程呢?
如果,买机票和叫车这两件事情我们让秘书来订,不管机票是在去哪网还是携程网买的,的士是手机app订的,还是路上拦的。我们(程序)只用做三件事情,让秘书订票,叫车,坐车去机场,从飞机从武汉到深圳。
所以,秘书在这个场景中是什么?就是一个已经可以使用的变量。至于这个秘书(变量)怎么去工作的,我们不需要知道。
先写这么多,第二个问题之后再来和大家讨论。
=================================
知乎用户
故事:猫的出生理解依赖注入 控制反转
大场景: 猫必须吃可以让他们变色的水果 才能成为真正不同颜色的猫! 比如橘猫要吃橘子!
场景: 猫的出生
强耦合关系:
猫A和上帝的故事:
上帝:你想成为高贵的橘猫,就必须自己生产橘子,我把秘方给你!你生产好了 自己吃掉 就能出生并且变成真正的橘猫了!
猫A:好!我这就生产。
猫A:死活生产不出来橘子,原来秘方是错的!
上帝:嘿嘿嘿~ 那就别出生了呗。你自己生产不出来。
猫A:妈卖批!
class 橘猫A{
public $猫吃的橘子;
public function __contruct(){
$this->猫吃的橘子 = new 橘子秘方;
}
}
橘子秘方{
}
依赖注入 解除耦合:
猫B和上帝的故事:
上帝:你想成为高贵的橘猫,就必须自己生产橘子,我把秘方给你!你生产好了 自己吃掉 就能出生并且变成真正的橘猫了!
猫B:我不要自己生产,猫A都 “妈卖批”了!你就不能生产好了再给我吃吗?
上帝尴尬:那好吧,答应你!
橘猫A(客串):妈卖批!
class 猫B{
public $猫吃的橘子;
public function __contruct(橘子){
$this->猫吃的橘子 = 橘子;
}
}
橘子秘方{
}
$橘猫B = new 猫B(new 橘子秘方);
控制反转
猫C和上帝的故事
上帝说:你想成为高贵的橘猫,就必须自己生产橘子,我把秘方给你!你生产好了 自己吃掉 就能出生并且变成真正的橘猫了!
猫C:别BB,你都给猫B上产了,还让我自己生产,有毛病吧?
上帝尴尬:呵呵- -~~~ 那你想怎样?
猫C:都特么想当橘猫~呵呵 我要当 ”变色猫! “
上帝:我日!有志气!
猫C:少BB 快来吧。
class 猫C{
public $猫吃的变色水果;
public function __contruct(变色水果){
$this->猫吃的变色水果 = 变色水果;
}
}
变色水果秘方(接口){
public function bianseinit(){}
}
上帝:上面就是给你的出生路径了,那你到底是想变成什么猫呢 橘猫 粉猫?
猫C:先来个 橘猫吧!
上帝:妈卖批!!!
橘子秘方 implements 变色水果秘方{
}
猫C:谢谢上帝 我还要 变成粉猫的果实!!
上帝:妈卖批
水蜜桃秘方 implements 变色水果秘方{
}
..
.. 贪婪无厌~!
$变色猫C = new 猫c(new 橘子秘方\水蜜桃秘方…….);
猫A(客串):妈卖批!
猫B(客串):妈卖批
故事结局END
上帝被痛扁一顿。
===============================
依赖注入
对于依赖注入,我是这样理解的,我拿我们的笔记本电脑举例:
我要把笔记本中的一个文件保存到外部存储器上,我的电脑有个usb接口。我只要拿出一个优盘/移动硬盘/USB接口读卡器并插上SD卡/USB接口的刻录机并有一张空白刻录盘……然后我就可以保存了。
依赖注入:我要存储文件到USB外部存储器“依赖”的是我插入(注入)的是什么usb设备。具体是什么,不是由笔记本决定,而是由使用USB外部存储器的用户插入的是什么决定:插入优盘、移动硬盘什么的都可以。笔记本厂商只要留个USB接口,剩下的优盘啊、移动硬盘怎么实现这个保存,笔记本厂商并不关心。这个是优盘、移动硬盘厂商的事情。这个就是依赖注入。
依赖注入是怎么实现解耦的?
对于笔记本厂商来说,我只要留个usb接口就行。以后优盘从16G升级到32G了,并不需要修改笔记本电脑的配置。
对于优盘厂商、移动硬盘厂商来说:我也只要我的设备上留个usb接口就行。至于插Dell电脑上,还是插Lenovo电脑上,插i3 CPU的电脑上还是i7 CPU的电脑上,没有关系。
我想程序员一般都懂点电脑吧,这个比喻应该很容易理解。
===========================================
作者:兴境
链接:https://www.zhihu.com/question/32108444/answer/1207207805
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
说说依(hun)赖(chi)注(deng)入(si)(DI,Dependency Injection)
通俗的说就是衣来伸手,饭来张口。像极了在家喊我妈的样子。
看看平常我在家是怎么喊我妈的,下面这个是我,能吃又能喝,好吃的好喝的都想要。
/** * 儿砸类 */ public class ErZa { public void chi(HaoChiDe haoChiDe){ System.out.println(haoChiDe.haoChi()); } public void he(HaoHeDe haoHeDe){ System.out.println(haoHeDe.haoHe()); } }
我的终极目标是混吃等死,就像下面这样:
/** * 混吃等死 */ public class HunChiDengSi { public static void main(String[] args) { ErZa erZa = new ErZa(); //儿砸喝可乐 erZa.he(new KeLe()); //儿砸吃饺子 erZa.chi(new JiaoZi()); } }
依赖注入的精华就在上面这最后一句话,我仔细解析一下:
依赖注入简直是混吃等死的经典之作了,我只需要张着嘴,就可以被投喂到各种各样的好吃的好喝的,只需要会吃会喝!
会做?不需要的!这辈子是不可能的。
什么都系好吃好喝我管不着,反正就是好吃、好喝就行:
public interface HaoChiDe { public String haoChi(); } public interface HaoHeDe { public String haoHe(); }
饺子好吃,实现了haoChiDe接口
/** * 饺子 */ public class JiaoZi implements HaoChiDe{ public String haoChi() { return “饺子好吃”; } }
可乐好喝,实现了haoHeDe接口
/** * 可乐 */ public class KeLe implements HaoHeDe{ public String haoHe() { return “可乐好喝”; } }
最后我们看看不混吃等死的奋斗青年是什么样的:
public class HaoErZa { //自己煮饺子 public void ziJiZhuJiaoZi() { System.out.println(“饺子好吃”); } //自己买可乐 public void ziJiMaiKeLe() { System.out.println(“可乐好喝”); } }
发现奋斗青年只会能吃能喝是不行, 还需要自己会做饭,这可就难了。完全没有躺着被喂到嘴边爽。
public class FenDou { public static void main(String[] args) { HaoErZa haoErZa = new HaoErZa(); haoErZa.ziJiMaiKeLe(); haoErZa.ziJiZhuJiaoZi(); } }
=========================================
A跟B结婚,生子C。C依赖A、B。
依赖注入就是A、B、C脱离父子、母子、夫妻关系,然后可以AC、AB、BC随意啪啪啪。
怎么脱离,就是如封神榜哪吒,销骨还父、削肉还母,三者解除血缘关系,代孕或是其他,然后类似于此
====================================
世上的路有千万条,而我却选择了最艰难的那一条
看完了答案,其实有一点很重要的没有说到,就是你自己注入的东西,你想怎么样改就怎么样改,好比我面试会问ioc和aop什么关系?
ioc最主要是解耦和方便附加功能(用aop实现)。
思想上
上帝说,要有光,于是有了光。谁给了上帝光,容器啊,你只需要知道你要什么(很多时候是接口),你不需要知道怎么来的的。
简单理解你就是一个富二代,你要什么不需要自己操心,开口就行,你的富爸爸(ioc容器)都给你包办了!爽不爽?
再具体一点,你对老爸说,我要一辆车,车就是接口Car,你老爸给了你一辆B字头的车,可能是比亚迪,也可能奔驰,比亚迪和背驰就是接口Car的具体实现,而你不需要知道具体是什么车,只需要知道是车就可以了。
====================================
…
控制反转后,假设用户想要一个苹果,就要在IoC容器中创建一个苹果对象,而苹果对象有名字,生产地址,价格等等属性,比如名字是String类型的,价格是int类型的,生产地址是Address类型的(这是你单独创建的一个类(bean),它也会被IoC容器创建),其实它还可以list,map,set,null等等类型,这些细节不重要,重要的是你要给这些属性赋值,这个赋值的操作也是通过IoC容器来完成,注入也就可以理解为你要向你创建的这个苹果对象中加入它的名字,价格等信息(赋值)。DI的主要方式是通过set注入的
或者这样想
依赖:bean对象的创建依赖于容器
注入:bean对象的所有属性由容器来注入
=====================================
…
控制反转后,假设用户想要一个苹果,就要在IoC容器中创建一个苹果对象,而苹果对象有名字,生产地址,价格等等属性,比如名字是String类型的,价格是int类型的,生产地址是Address类型的(这是你单独创建的一个类(bean),它也会被IoC容器创建),其实它还可以list,map,set,null等等类型,这些细节不重要,重要的是你要给这些属性赋值,这个赋值的操作也是通过IoC容器来完成,注入也就可以理解为你要向你创建的这个苹果对象中加入它的名字,价格等信息(赋值)。DI的主要方式是通过set注入的
或者这样想
依赖:bean对象的创建依赖于容器
注入:bean对象的所有属性由容器来注入
===========================