[看完就懂] Laravel 服务容器,IoC,DI
DI
DI就是常说的依赖注入,那么究竟什么是依赖注入呢?
打个比方,电脑(非笔记本哈)需要键盘和鼠标我们才能进行操作,这个‘需要’换句话说就是‘依赖’键盘和鼠标。
那么,相应的,一个类需要另一个类才能进行作业,那么这也就是依赖。
看一段代码:
class Computer {
protected $keyboard;
public function __construct() {
$this->$keyboard = new Keyboard();
}
}
这里的Computer类依赖了键盘类。
好,既然我们已经知道了什么是依赖,那么什么是注入呢?
我们改造一下上面的代码:
class Computer {
protected $keyboard;
public function __construct(Keyboard $keyboard) {
$this->$keyboard = $keyboard;
}
}
$computer = new Computer(new Keyboard());
这里的Computer类依赖注入了Keyboard类。
关于依赖注入,我的理解是:
所需要的类通过参数的形式传入的就是依赖注入。
理解了依赖注入,我们可以接着理解IOC。
IOC
IOC是什么呢?
中文叫控制反转。啥意思呢? 这个看明白了DI后就能很容易的理解了。
通过DI我们可以看到,一个类所需要的依赖类是由我们主动实例化后传入类中的。
控制反转和这个有什么关系呢?
控制反转意思是说将依赖类的控制权交出去,由主动变为被动。
看一段laravel代码:
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class SessionController extends Controller
{
public function login(Request $request)
{
//这就是IOC,我们不需要主动传入类了一切由laravel去实现
}
}
看到这你可能有疑问了,这是怎么实现的呢?
这就是靠服务容器了,请往下接着看。
服务容器
看了很多文章,我一致认为服务容器就是一种设计模式。
它的目的就是解耦依赖。
它有点类似于我前面说的《享元模式》。区别在于服务容器解决了所有依赖的实现。
这里我们再从头至尾的看一遍,怎么一步步演化出服务容器。
依然是电脑的例子,我们知道电脑依赖键盘鼠标,可是键盘鼠标也有很多种呀。
先看一个最原始的代码例子:
class Computer {
protected $keyboard;
public function __construct($type = null) {
switch($type) {
case 'common':
$this->keyboard = new CommonKeyboard();
case 'awesome':
$this->keyboard = new AweSomeKeyboard();
default:
$this->keyboard = new Keyboard();
}
}
}
或许你一眼就看出了问题在哪。
如果我们又要增加一种键盘,那我们又得对这个类进行修改。这样下去,这个类会变得庞大且耦合程度过高。
那么我们可以怎么修改呢?
- 工厂模式
这样我们可以避免直接的修改Computer类。
简单工厂
class Factory {
public static function getInstance($type){
switch($type) {
case 'common':
$this->keyboard = new CommonKeyboard();
break;
case 'awesome':
$this->keyboard = new AweSomeKeyboard();
break;
default:
$this->keyboard = new Keyboard();
break;
}
}
}
class Computer {
protected $keyboard;
public function __construct($type == null) {
$this->keyboard = Factory::getInstance($type);
}
}
这样使用简单工厂模式后,我们后续的修改可以不用对Computer类进行操作而只要修改工厂类就行了。这就相当于对Computer类进行了解耦。
Computer类虽不在依赖那些键盘类了,但是却变为依赖工厂类了。
后续添加新类型的键盘就必须对工厂类进行修改。
所以这个工厂类还不能很好的满足要求,我们知道电脑对键盘的接口都是一致的,键盘必须实现这一接口才能被电脑识别,那我们对Computer和Keyboard类进行修改。
- DI(依赖注入)
interface Board {
public function type();
}
class CommonBoard implements Board {
public function type(){
echo '普通键盘';
}
}
class MechanicalKeyboard implements Board {
public function type(){
echo '机械键盘';
}
}
class Computer {
protected $keyboard;
public function __construct (Board $keyboard) {
$this->keyboard = $keyboard;
}
}
$computer = new Computer(new MechanialKeyBoard());
可是这样也有问题,如果我们后续对这台电脑使用的键盘不满意要进行替换呢? 我们又回到原点了,必须去修改传入的键盘类。
能不能做成可配置的呢?
- IOC服务容器(超级工厂)
class Container
{
protected $binds;
protected $instances;
public function bind($abstract, $concrete)
{
if ($concrete instanceof Closure) {
$this->binds[$abstract] = $concrete;
} else {
$this->instances[$abstract] = $concrete;
}
}
public function make($abstract, $parameters = [])
{
if (isset($this->instances[$abstract])) {
return $this->instances[$abstract];
}
array_unshift($parameters, $this);
return call_user_func_array($this->binds[$abstract], $parameters);
}
}
这就是一个简单的IOC服务容器。
这个怎么解决我们上述的问题呢?
$container = new Container;
$container->bind('Board', function($container){
return new CommonBoard;
});
$container->bind('Computer',function($container,$module){
return new Computer($container->make($module));
});
$computer = $container->make('Computer',['Board']);
这里生产出来的Computer类就是一个使用普通键盘的电脑类了。
解释一下代码:
bind(name,function($container){
return new Name;
})
这里的name和Name之间的关系是:
当我需要name类的时候你就给我实例化Name类。
make(name)方法是对name进行生产返回一个实例。
如果我们要更换键盘怎么办呢?
$container->bind('Board', function($container){
return new MechanicalBoard;
});
$container->bind('Computer',function($container,$module){
return new Computer($container->make($module));
});
$computer = $container->make('Computer',['Board']);
只要对bind绑定的Board类的实现进行修改,我们就可以很容易替换掉键盘了。这就是一个服务容器。
对服务容器进行一个理解:
容器就是一个装东西的,好比碗。而服务就是这个碗要装的饭呀,菜呀,等等东西。当我们需要饭时,我们就能从这个碗里拿到。如果你想在饭里加点菜(也就是饭依赖注入了菜),我们从碗里直接拿饭就可以了,而这些依赖都由容器解决了(这也就是控制反转)。
我们需要做的就是对提供的服务进行维护。
我们看一段真实的在laravel框架上能跑的代码:
当然laravel框架的服务容器比这里的要复杂很多了,但我们明白了它的使用目的以及使用场景就不难去入手laravel了。
PS:你的赞是我创作的动力!
本作品采用《CC 协议》,转载必须注明作者和本文链接
高认可度评论:
要是还没看懂怎么办,还有的救吗
我就喜欢这种深入浅出的文章
@雪风 恭喜首评:smile:,献上香吻一个:kissing:
要是还没看懂怎么办,还有的救吗
依赖注入一般是靠PHP的反射类来实现的,可以实现无限级依赖的注入。
非常棒
@不温柔 谢谢指正。hah
@tsin 是的
@BradStevens :smile:
@wanghan 多敲代码:smile:
https://implode.io/4mT8O4 这个网站不错,还能直接运行laravel 代码
$container = new $container;
是否应为
$container = new Container;
@echofree313 是的,写错了:smile_cat:
$container->bind('Computer',function($container,$module){
return new Computer($container->make($module));
});
求问:$module参数 指什么。
看不太懂这一步。求解释。
@echofree313 所依赖的模块
我还是有点不理解。哈哈。好像又发现了一个错误的地方。
$computer = $container->make('Computer','Board');
应为
$computer = $container->make('Computer',['Board']);
私信楼主了。怎么添加个联系方式,求问下。
@echofree313 you are right
@echofree313 这里只要理解为什么要有服务容器以及它的应用场景就好了,技术细节还要参考其他书籍
其实 我是不理解 绑定 computer。绑定键盘类是理解的。绑定computer时候,里面的$container->make($module),不理解运行过程,可以给个具体的过程分析下么。这个貌似运行不成功吧?有点菜鸡。求指教。
@echofree313 可以运行的,用debug工具对每一步打下断点就可以明白运行流程了。
楼主,服务容器的基本原理知道了,但在 Laravel 中怎么具体应用的,能举个例子吗?
比如,控制器中的非构造方法是怎么实现自动注入的,如果能有一篇文分析下完整过程,那就太棒了。
@hehorange 好的,争取在年前写一篇相关的文章:smile:
@simpleT ↖(^ω^)↗期待,先点赞了
Factory类里面static方法不能用this吧。。。。。而且并没有return
@lawrencepu sorry ,已修正
@lovecn 很强大噢
解释的很好, 清晰易懂
眼前一亮,依赖 和依赖 注入 ,容器 有加深了一点印象。
台式电脑 需要 键盘 依赖 (内部 依赖 外部 没有 键盘 打不字)
不同键盘 注入 到 电脑( 生活有了改变,可以用上了 好的键盘 ,有了这个给外部的键盘注入进来)
有所收获
这里是不是应该是一个等号?
@guansixu 对的。。。。
Laravel 容器中,依赖解析的关键代码是:https://github.com/laravel/framework/blob/... ,使用类反射并不断递归,实现 依赖的[依赖的][依赖的][...] 自动解析。
原文应该在这里吧:https://www.insp.top/learn-laravel-contain...
论坛很多关于这个概念的文章,这篇是我个人觉得最棒的。
非常感谢楼主的文章,深入浅出。
@shawn805 我也看了这篇文章,思路一样,不知道是谁先写谁后写
@shawn805 我也看了这篇文章,思路一样,不知道是谁先写谁后写
@shawn805 这里的是简化修改版,原文一系列笔记真不错
这个哪来的呢?
@WaKA 看上面的步骤里
:+1: 比喻贴切,导入也清晰。加深了我对依赖注入、服务容器的理解,感谢分享。
@Thosege :grinning:
刚又看了遍,服务容器举例中
$this->$keyboard = new 键盘类?
@Thosege :joy:对的
@维C 哥哥我只接受程序嫒的香吻,秃顶兄弟走开。
这里打错了
composer类注入Board $keyboard这里看不懂,这个Board不是interface吗,依赖注入还可以注入interface啊?
搂主可以列举一点实例来对比一下用了服务容器的好处吗 这样我感觉会比较深刻一点
对比了楼主的举例,依赖注入 和 服务容器, 依赖注入要更换键盘这样既可: $computer = new Computer(new MechanialKeyBoard()); $computer = new Computer(new CommonBoard()); 而服务容器: $container->bind('Board', function($container){ return new MechanicalBoard; }); $container->bind('Board', function($container){ return new CommonBoard; }); 同样只要修改一处地方,这不完全没区别吗?服务容器的代码还更加复杂难懂; 服务容器的优势应该不是这里吧?楼主的举例好像没能说明服务容器的优势 楼主的说明通俗易懂,但麻烦楼主再斟酌下优势的说明,这样一对比我没看到服务容器的优势
生成容器了,怎么使用呢,调用方法