laravel服务容器-----深入理解控制反转(IoC)和依赖注入(DI),facade(门面)和contracts(契约)联系

一.什么是依赖注入和控制反转?


控制反转:即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 中的许多类型的类通过(契约模式)的 服务容器 来解析,包括控制器,事件侦听,中间件,队列作业,甚至路由闭包等。那么,要获取一个合同的实现,你只需在要解析的类的构造方法中键入『类型提示』的接口。

 

 

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值