一、名词解释
IoC - Inversion of Control 控制反转
DI - Dependency Injection 依赖注入
依赖倒置原则(DIP)
:一种软件架构设计的原则(抽象概念),依赖倒置原则,它转换了依赖,抽象模块不依赖于具体模块的实现,而具体模块依赖于抽象模块定义的接口。通俗的讲,就是抽象模块定义接口,具体模块负责实现。。
控制反转(IoC)
:一种反转流、依赖和接口的方式(DIP的具体实现方式)。
依赖注入(DI)
:IoC的一种实现方式,用来反转依赖(IoC的具体实现方式)。
IoC容器
:依赖注入的框架,用来映射依赖,管理对象创建和生存周期(DI框架)。
依赖注入(DI)是一个过程,通过这个过程,对象可以通过构造函数参数,工厂方法的参数或者在构造或返回对象实例后设置的属性来定义它们的依赖关系从工厂方法。
依赖注入和控制反转说的实际上是同一个东西,它们是一种设计模式,这种设计模式用来减少程序间的耦合。依赖注入和控制反转是对同一件事情的不同描述,从某个方面讲,就是它们描述的角度不同。
1、依赖注入是从应用程序的角度在描述,可以把依赖注入,即:应用程序依赖容器创建并注入它所需要的外部资源;
2、而控制反转是从容器的角度在描述,即:容器控制应用程序,由容器反向的向应用程序注入应用程序所需要的外部资源。
整个过程中参与者都有谁?
一般有三方参与者,一个是某个对象;一个是IoC/DI的容器;另一个是某个对象的外部资源。
1、某个对象指的就是任意的、普通的PHP对象;
2、IoC/DI的容器简单点说就是指用来实现IoC/DI功能的一个框架程序;
3、对象的外部资源指的就是对象需要的,但是是从对象外部获取的,都统称资源,比如:对象需要的其它对象、或者是对象需要的文件资源等等。
二、控制反转IoC进一步解释
1、如何反转,反转了什么
控制反转顾名思义,就是要去反转控制权,那么到底是哪些控制被反转了?控制反转是指把对象的依赖管理从内部转移至外部。
在单一职责原则的设计下,很少有单独一个对象就能完成的任务。大多数任务都需要复数的对象来协作完成,这样对象与对象之间就有了依赖。一开始对象之间的依赖关系是自己解决的,需要什么对象了就New一个出来用,控制权是在对象本身。但是这样耦合度就非常高,可能某个对象的一点小修改就会引起连锁反应,需要把依赖的对象一路修改过去。
如果依赖对象的获得被反转,具体生成什么依赖对象和什么时候生成都由对象之外的IOC容器来决定。对象只要在用到依赖对象的时候能获取到就可以了,常用的方式有依赖注入和依赖查找(Dependency Lookup)。这样对象与对象之间的耦合就被移除到了对象之外,后续即使有依赖修改也不需要去修改原代码了。
2、依赖注入
控制反转是把对象之间的依赖关系提到外部去管理,可依赖是提到对象外面了,对象本身还是要用到依赖对象的,这时候就要用到依赖注入了。顾名思义,应用需要把对象所需要的依赖从外部注入进来。IoC有2种常见的实现方式:依赖注入和服务定位。
控制反转(IoC)一种重要的方式,就是将依赖对象的创建和绑定转移到被依赖对象类的外部来实现。
依赖注入(DI),它提供一种机制,将需要依赖(具体模块)对象的引用传递给被依赖(抽象模块)对象。
实现方式:
方法一
构造函数注入
构造函数函数注入传递依赖。根据DIP原则,我们知道抽象模块不应该依赖于具体模块,两者应该依赖于抽象。那么构造函数的参数应该是一个抽象类型。
方法二
属性注入
属性注入是通过属性来传递依赖。
方法三
接口注入
相比构造函数注入和属性注入,接口注入显得有些复杂,使用也不常见。具体思路是先定义一个接口,包含一个设置依赖的方法。然后依赖类,继承并实现这个接口。
对于大型项目来说,相互依赖的组件比较多。如果还用手动的方式,自己来创建和注入依赖的话,显然效率很低,而且往往还会出现不可控的场面。正因如此,IoC容器诞生了。IoC容器实际上是一个DI框架,它能简化我们的工作量。
它包含以下几个功能:
1、动态创建、注入依赖对象。
2、管理对象生命周期。
3、映射依赖关系。
三、PHP代码示例
PHP具体代码实现的思路:
1、Ioc容器维护binding数组记录bind方法传入的键值对如:log=>FileLog, user=>User
2、在ioc->make('user')的时候,通过反射拿到User的构造函数,拿到构造函数的参数,发现参数是User的构造函数参数log,然后根据log得到FileLog。
3、这时候我们只需要通过反射机制创建 $filelog = new FileLog();
4、通过newInstanceArgs然后再去创建new User($filelog);
interface log{
public function write();
}
// 文件记录日志
class FileLog implements Log{
public function write(){
echo 'file log write...';
}
}
// 数据库记录日志
class DatabaseLog implements Log{
public function write(){
echo 'database log write...';
}
}
class User{
protected $log;
public function __construct(Log $log){
$this->log = $log;
}
public function login(){
// 登录成功,记录登录日志
echo 'login success...';
$this->log->write();
}
}
class Ioc{
public $binding = [];
public function bind($abstract, $concrete){
//这里为什么要返回一个closure呢?因为bind的时候还不需要创建User对象,所以采用closure等make的时候再创建FileLog;
$this->binding[$abstract]['concrete'] = function ($ioc) use ($concrete) {
return $ioc->build($concrete);
};
}
public function make($abstract){
// 根据key获取binding的值
$concrete = $this->binding[$abstract]['concrete'];
return $concrete($this);
}
// 创建对象
public function build($concrete) {
$reflector = new ReflectionClass($concrete);
$constructor = $reflector->getConstructor();
if(is_null($constructor)) {
return $reflector->newInstance();
}
$dependencies = $constructor->getParameters();
$instances = $this->getDependencies($dependencies);
return $reflector->newInstanceArgs($instances);
}
// 获取参数的依赖
protected function getDependencies($paramters) {
$dependencies = [];
foreach ($paramters as $paramter) {
$dependencies[] = $this->make($paramter->getClass()->name);
}
return $dependencies;
}
}
//实例化IoC容器
$ioc = new Ioc();
$ioc->bind('log','FileLog');
$ioc->bind('user','User');
$user = $ioc->make('user');
$user->login();
参考
1、聊一聊PHP的依赖注入(DI) 和 控制反转(IoC)
2、原则&模式|理解DIP、IoC、DI以及IoC容器
3、Laravel 服务容器实例教程 —— 深入理解控制反转(IoC)和依赖注入(DI)(推荐阅读
)
4、如何实现 IoC 容器和服务提供者是什么概念(推荐阅读
)
5、 [Laravel 5.8 文档 ] 底层原理 —— 服务容器(推荐阅读
)
6、PHP反射机制实现自动依赖注入
7、那些年搞不懂的高深术语——依赖倒置•控制反转•依赖注入•面向接口编程
8、控制反转(IOC)和依赖注入(DI)(推荐阅读
)
9、控制反转和依赖注入的理解
10、谈谈php里的IOC控制反转,DI依赖注入