16年的时候整理过一篇IOC的实现原理,在进一步之前可先 查看
然后结合laravel再来看IOC的实现,便会有一种 原来是这样
的感受。从而也会对一直不接触的设计模式有根清晰的认识。
先来看几个名词
- 依赖注入 (dependency injection)
- 控制反转 (Inversion Of Control, IOC)
依赖注入
这是一个很花哨的名词,如很多设计模式一样,但不了解具体实现的时候就会觉得雨里雾里,不知所云。
先来看一段简单的代码,也是很「经典」的代码
class UserController
{
publc function getUserInfo($id)
{
$model = new UserDao;
return $model->where('id', $id)->first();
}
}
也许你看出来了,所谓「经典」并不是因为代码有奇妙之处,而是,这样的代码在很多「工程师」的代码里,随处可见。
这样的代码一个很明显的问题便是,「高级代码」依赖「低级代码」,「低级代码」如果出错,哪「高级代码」就完全用不了。
{tip} 「高级代码」 业务逻辑代码实现,也就是最终呈现给用户的代码。 「低级代码」 数据库操作,文件操作,网络操作等一切为业务服务的代码。
来看一段改进
class UserController
{
protected $user;
publc function getUserInfo(User $user, $id)
{
return $user->where('id', $id)->first();
}
}
修改以后的业务代码,不需要知道数据从何而来,只需要知道从一个 User
对象能获取到所需要的数据。
这样的方式便叫「依赖注入」,「我」依赖你,但并不是离了「你」就不能活。
控制反转
控制反转是依赖注入的一种改进,在上面的代码中,「高级代码」 想要获取到「低级代码」数据,必须手动传入「User」,这样当「User」作出修改,哪「高级代码」依赖的部分也要做出修改。
利用「控制反转」来实现
class UserController
{
protected $user;
publc function getUserInfo(UserInterface $user, $id)
{
return $user->where('id', $id)->first();
}
}
这里注入的是一个「接口」,「高级代码」不依赖具体的实现,而依赖的是实现的接口。这样无论「低级代码」作出怎样的修改,「高级代码」都不需要做任何修改。
这就是「面向对象」常说的:要面向接口编程,而不是面向实现。
Laravel容器实现
容器的终极目标就是解决「依赖」。让「低级代码」的实现,不会出现在「高级代码」的逻辑中。
通过Laravel的「Route」模块,来分析容器的实现。看 routes/web.php
的内容
以前,每当学习一个框架的时候,首先看的是控制器,因为比较容易看到效果。
Route::get('/',funtion(){
return view('welcome');
})
初学者往往会以为这里就是框架的入口—因为我们写的业务代码确实是从这里开始的,从而导致了很多人认为请求直接就到了这里。其实不然。
既然 web.php
里可以使用 Route::method
,通过 这篇文章 可以知道,既然这里可以使用,必然有地方进行了「绑定」。
有一个现成的「轮子」,Illuminate\Container
(容器) Laravel框架的核心程序便是继承了这个「轮子」,然后把「其他模块」通过「绑定」的方式,组合进了框架。所以 Laravel
的核心代码很少。其中「路由」,「请求」,「响应」等等各个模块都是通过「绑定」的方式组合到一起实现强大的功能。
在进一步之前,确保你已经看懂了Laravel的文档,请求的生命周期,很多时候最好的学习资料是框架本身提供的文档。
Laravel 通过 Illuminate\Container
提供的 bind
instance
singleton
等等方式进行「注入」到容器里,再通过make
获取到注入的「实例」「匿名函数」「接口实现」等。
本来打算写每个函数的用法,然后发现,无论怎么写,都没有官方文档总结的到位。遂放弃,所以建议多读几遍官方文档吧。
接着上面的 Route
继续看,通过查看 Illuminate\FoundationApplication
可以看到,Laravel通过「Container」注入了路由的「服务提供者」,并把「Container」传给了「服务提供者」
$this->register(new RoutingServiceProvider($this));
而后由 RoutingServiceProvider
自己注册实现和所需要的依赖。
class RoutingServiceProvider extends ServiceProvider
{
public function register()
{
$this->registerRouter();
$this->registerUrlGenerator();
$this->registerRedirector();
$this->registerPsrRequest();
$this->registerPsrResponse();
$this->registerResponseFactory();
}
这时候,我们便可以通过 $app['route']
或者 $app->route
来获取到路由的实现,这会执行「容器」的 make
方法。获取到之前注册的内容。
而在web.php
里没有这样写,这就涉及到另一个概念 Facade
。
看到配置文件 bootstrap/app.php
aliases' => [
'Route' => Illuminate\Support\Facades\Route::class
]
当使用 Route
的实际调用的是 Illuminate\Support\Facades\Route::class
, Route::method
执行的就是 Illuminate\Support\Facades::__callStatic
public static function __callStatic($method, $args)
{
$instance = static::getFacadeRoot();
if (! $instance) {
throw new RuntimeException('A facade root has not been set.');
}
return $instance->$method(...$args);
}
其中
$instance = static::getFacadeRoot();
最终调用
protected static function resolveFacadeInstance($name)
{
if (is_object($name)) {
return $name;
}
if (isset(static::$resolvedInstance[$name])) {
return static::$resolvedInstance[$name];
}
return static::$resolvedInstance[$name] = static::$app[$name];
}
可以看到,其实就是调用了 $app[name]
;
到此,通过容器的分析,便可以了解laravel的核心概念了。