一.什么是依赖注入和控制反转?
控制反转:即IOC (Inversion of Control),它把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。所谓的“控制反转”概念就是对组件对象控制权的转移,从程序代码本身转移到了外部容器。
依赖注入:基本原则是:应用组件不应该负责查找资源或者其他依赖的协作对象。配置对象的工作应该由IoC容器负责,“查找资源”的逻辑应该从应用组件的代码中抽取出来,交给IoC容器负责。
依赖注入(Dependency Injection)和控制反转(Inversion of Control)是同一个概念。
具体含义是:当某个角色(可能是一个PHP实例,调用者)需要另一个角色(另一个PHP实例,被调用者)的协助时,在 传统的程序设计过程中,通常由调用者来创建被调用者的实例。但在laravel里,创建被调用者的工作不再由调用者来完成,因此称为控制反转;创建被调用者实例的工作通常由laravel容器来完成,然后注入调用者,因此也称为依赖注入。
二. 实例解释
不管是依赖注入,还是控制反转,都说明laravel采用动态、灵活的方式来管理各种对象。对象与对象之间的具体实现互相透明。在理解依赖注入之前,看如下这个问题在各种社会形态里如何解决:一个人(php实例,调用者)需要一把斧子(php实例,被调用者)。
(1) 原始社会里,几乎没有社会分工。需要斧子的人(调用者)只能自己去磨一把斧子(被调用者)。对应的情形为:php程序里的调用者自己创建被调用者。
这种情况下,php实例的调用者创建被调用的php实例,必然要求被调用的php类出现在调用者的代码里。无法实现二者之间的松耦合。
(2) 进入工业社会,工厂出现。斧子不再由普通人完成,而在工厂里被生产出来,此时需要斧子的人(调用者)找到工厂,购买斧子,无须关心斧子的制造过程。对应php程序的简单工厂的设计模式。
这种情况下,调用者无须关心被调用者具体实现过程,只需要找到符合某种标准(接口)的实例,即可使用。此时调用的代码面向接口编程,可以让调用者和被调用者解耦,这也是工厂模式大量使用的原因。但调用者需要自己定位工厂,调用者与特定工厂耦合在一起。
(3) 进入“按需分配”社会,需要斧子的人不需要找到工厂,坐在家里发出一个简单指令:需要斧子。斧子就自然出现在他面前。对应php的依赖注入。
这种情况下,调用者无须自己定位工厂,程序运行到需要被调用者时,系统自动提供被调用者实例。事实上,调用者和被调用者都处于laravel的管理下,二者之间的依赖关系由laravel提供。
三.总结:
所谓依赖注入,是指程序运行过程中,如果需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部的注入。laravel的依赖注入对调用者和被调用者几乎没有任何要求。
-----------------------------------------------------------------实战-----------------------------------------------------------------------
外观
(门面
)是外观模式
的实现。协议
(契约
)是工厂方法模式
或抽象工厂模式
的实现。
(一)门面模式(Facade)
为了让 Laravel 中的核心类使用起来更加方便,Laravel实现了门面模式。
外观模式(Facade pattern),是软件工程中常用的一种软件设计模式,它为子系統中的一组接口提供一个統一的高层接口,
使得子系統更容易使用。 — 维基百科
Laravel 中的使用
我们使用的大部分核心类都是基于门面模式实现的。例如:
$value = Cache::get('key');
这些静态调用实际上调用的并不是静态方法,而是通过 PHP 的魔术方法__callStatic() 讲请求转到了相应的方法上。
总之,Facade只是用静态方式简化了这个使用过程,底层用__callStatic
把函数和参数传给服务实例.
那么如何讲我们前面写的服务提供器也这样使用呢?方法很简单,只要这么写:
use Illuminate\Support\Facades\Facade;
class Foo extends Facade {
protected static function getFacadeAccessor() { return ‘foo’; }
}
这样我们就可以通过 Foo::test() 来调用我们之前真正的 FooBar 类的方法了。
别名(Alias)
有时候我们可能将 Facade 放在我们扩展库中,它有比较深的命名空间,如:\Library\MyClass\Foo。这样导致使用起来并不方便。Laravel 可以用别名来替换掉这么长的名字。
我们只要在 app/config/app.php 中 aliases 下增加一行即可:
'aliases' => [
…
'Foo' => ‘Library\MyClass\Foo’,
],
这样它的使用就由 \Library\MyClass\Foo::test() 变成 Foo::test() 了。
(二)控制反转(Inversion of Control)(Contracts 契约模式)
什么是 IoC
控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。
其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。
通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递给它。 — 维基百科
简单说来,就是一个类把自己的的控制权交给另外一个对象,类间的依赖由这个对象去解决。依赖注入属于依赖的显示申明,而依赖查找则是通过查找来解决依赖。
通过这张图我们可以看到,当写好自定义的Contract接口及其实现类后,在ServiceProvider中绑定,此时服务容器已经登记上这个Contract了。之后就可以在要用到它的地方,经过服务容器解析直接使用了。
下面就详细写一下怎么具体的使用:
第一步,写一个Contract接口:
<?php
namespace App\Contracts;
interface Hello
{
public function hello();
}
第二步,写上面Contract的实现类:
<?php
namespace App\Services;
use App\Contracts\Hello;
class HelloWorld implements Hello
{
function hello(){
return "Hello!~~";
}
}
第三步,写一个自定义的ServiceProvider:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class HelloServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
//
}
public function register()
{
//给这个接口一个别名
$this->app->bind('Hello','App\Contracts\Hello');
//将Contract接口和它的实现类绑定
$this->app->bind
('App\Contracts\Hello','App\Services\HelloWorld');
}
}
这里起别名的作用,是为了在使用的时候方便,不需要写完整的命名空间;绑定的作用是为了使用Contracts时,服务容器能够有线索找到它的实现类,从而解析出来。
第四步,在config\app.php中注册这个服务提供者:
在providers中加入这行代码即可:
App\Providers\HelloServiceProvider::class,
第五步,可以使用了:
<?php
namespace App\Http\Controllers;
use App\Contracts\Hello;
class DiaryController extends Controller
{
/**
* 测试:Hello实例
*
* @var Hello
*/
protected $hello;
/**
* Create a new controller instance.
* 创造一个Hello实例
*
* @param Hello $hello
* @return void
*/
public function __construct(Hello $hello){
$this->hello=$hello;
}
/**
* Display a list of all of the user's diaries.
*
* @return Response
*/
public function index(){
return view('diaries.index',[
'hello'=>$this->hello->hello(),
]);
}
}
之后就可以在返回的视图中通过{{$hello}}来打印出Contract实现类中的返回内容了。
这里对第五步做几点说明:
在构造方法中,将Contract接口引入(注入)这里参数中‘Hello’就是刚才起的别名。
因为刚才已经在服务提供者中绑定了Contract和其实现方法,所以这里能够通过Hello这个Contract,解析并使用其实现类中的方法hello()。
总结
Contract(契约模式)就是一堆框架自带的接口,可以通过依赖注入得到具体实现。Facade和Contract只是依赖注入容器的不同使用方式,用Facade就是自己去容器取(把容器当Service Locator用),用Contract就是等容器注入依赖。所以有了门面模式(Facade)和控制反转(Inversion of Control),实际还有别名(Alias)和 服务提供器(Service Providers),我们创建自己的类库和扩展 Laravel 都会方便很多。
何时使用 契约 Vs. Facades
Facades 有很多优点,它提供了简单,易记的语法,从而无需手动注入或配置长长的类名。此外,由于他们对 PHP 静态方法的独特调用,使得测试起来非常容易。
然而,在使用 Facades 时,有些地方需要特别注意。使用 Facades 时最主要的危险就是会引起类作用范围的膨胀。由于 Facades 使用起来非常简单并且不需要注入,就会使得我们不经意间在单个类中使用许多 Facades ,从而导致类变得越来越大。然而使用依赖注入的时候,使用的类越多,构造方法就会越长,在视觉上注意到这个类有些庞大了。因此在使用 Facades 的时候,要特别注意控制类的大小,让类的作用范围保持短小。
Facades 和辅助函数提供了一种简便方式来使用 Laravel 服务而无需用到类型提示,也可在服务容器外部解析契约。多数情况下,每个 Facade 都有一个等效的契约。
和 Facades (不须要在你类中的构造函数去引用依赖)不同的是,契约允许你给自己的类定义明确的依赖。一些开发者更喜欢依赖被明确地定义出来,所以更倾向于使用契约,而其他开发者则享受于 Facades 带来的方便。
综上所述,使用契约还是 Facades 很大程度上取决于你个人或者团队的喜好。契约和 Facades 均可以用来构建健壮的、充分测试过的 Laravel 应用。只要你保持类的职责单一,你会发现使用契约和 Facades 的实际差别是非常小的。
Tip:在开发与 Laravel 进行交互的第三方扩展包时,最好选择注入 Laravel 契约 而不使用 Facades 。
因为扩展包是在 Laravel 之外构建,你无法使用 Laravel Facades 测试辅助函数,
所以你在搭建扩展包,那你应该强烈考虑使用契约,因为他们更便于在包的上下文中做测试。
Laravel 中的许多类型的类通过(契约模式)的 服务容器 来解析,包括控制器,事件侦听,中间件,队列作业,甚至路由闭包等。那么,要获取一个合同的实现,你只需在要解析的类的构造方法中键入『类型提示』的接口。