看laravel学习依赖注入

前言

初心

最近在看设计模式中的依赖注入,希望借助设计模式的神奇魔力,能达到一个目的,然后在此学习的过程中,能收获一个bonus。
这个目的就是能使得自己设计的系统更简单更容易理解,或者是使得系统设计的结构和代码更简单,而bonus是企图在设计模式上实现概念上的并发。
这篇文章是希望把自己这段时间的学习成果作一个记录和总结,然而并不要太期待,因为目前得出的结论并没有达到我的目的,bonus暂时希望也比较渺茫。

在知识网络结构认知上的准备

我们需要提前了解一下依赖倒置原则控制反转依赖注入的关系。
控制反转是一种设计思想,它遵循了依赖倒置原则,而实现控制反转一般主要的方式或者手段是依赖注入
依赖倒置原则本文就不作解释了,我们了解一下即可。

唠一唠依赖注入和IoC

依赖注入是什么东西呢

这里有几个先行的概念需要描述一下,首先我们要有一个主体,这个主体注入依赖主体通过使用这些依赖去做一些行为或者实施一些操作。
主体指的是一些系统设计上的一些实体,比如有一个违规记录类,用于实现创建、修改、审核的业务逻辑,那么这个违规记录类就是一个主体,再比如有一个这样的业务逻辑:违规记录内包含多个违规指标。这时候这个违规指标类,也是一个主体
依赖指的是主体需要依赖另外某个主体实现某个功能,比如刚刚的例子里的违规记录类需要依赖违规指标类去实现创建违规记录的逻辑,创建的逻辑里需要记录该条违规记录有哪些违规指标,以及需要作违规指标的业务校验,这里违规记录类这个主体需要依赖违规指标类这个主体实现一些动作,那么违规指标类这个主体就是违规记录类这个主体依赖
至于注入,指的是主体如何获取依赖,常见的方式有通过构造函数参数传入和类方法参数传入,简单而言是这样:

依赖 = new 主体B();
对象 = new 主体A(依赖);
对象.实现功能();
IoC容器
啥是IoC

什么是Inversion Of Control(控制反转)呢,这里我们要先理解一下控制正转,对其他主体或者流程的控制是完全限制在主体以内的就是控制正转,其实也就是我们平时写代码的姿势,举例来说就是:

class A
{
  public function 实现功能()
  {
    对象B = new B();
    对象B.检查一下商品数量();
    对象C = new C();
    对象C.检查一下优惠();
    return "牛逼!";
  }
}

对象A = new A();
对象A.实现功能();

控制反转是指的对其他主体的控制不在主体以内,主体并不需要亲自创建对象,而是通过注入的方式获得对象,是的,上面依赖注入的例子就是控制反转,一般会使用依赖注入来实现控制反转,举例来说就是:

class A
{
  B 对象B;
  C 对象C;
  public __construct(B 对象B, C 对象C)
  {
    this.对象B = 对象B;
    this.对象C = 对象C;
  }
  public function 实现功能()
  {
    this.对象B.检查一下商品数量();
    this.对象C.检查一下优惠();
    return "牛逼!";
  }
}

// 同时也是依赖注入的例子
对象B = new B();
对象C = new C();
对象A = new A(对象B, 对象C);
对象A.实现功能();
为什么要有IoC容器,要实现什么效果

好的!介绍完IoC的概念,那什么是IoC容器呢,其实就是将创建对象的工作收到一个”容器“内,这样代码就会变得更加简单和简洁。
在上面的例子的基础上,要实现的效果简单来说就是:

// 类A代码没有变动

容器 = new 容器();
容器.绑定(B);
容器.绑定(C);
容器.绑定(A);

对象A = 容器.制造(A);
对象A.实现功能();

laravel IoC 容器详解

使用 IoC 容器注入依赖,我们可以更加方便的管理和使用依赖,这里有必要研究一下IoC容器的具体实现,我们可以找一找业界有名的实现:laravel的 IoC 容器,探一探其中究竟。
IoC容器的基本动作有两个,绑定和生产对象,我们可以基于这两个容器的基本操作,来进入IoC容器的内部。

IoC容器的基本操作

基本使用方法

laravel IoC容器的使用方法基本和上述容器的使用例子一致,一个是对主体进行绑定,这里的主体指的是某个类或者是某个对象;另外一个是创建对应的对象,才能使用该对象完成各种业务逻辑或者功能。

绑定

laravel 的容器会维护多个绑定关系,用于在生产对象的时候解析到对应的类,才能创建出这个类的对象,绑定关系大概有以下几种,有兴趣的读者可以查阅container类的属性,这里对绑定关系及其调用关系作一个简单的介绍,以供参考:

  • $resolved: 记录某个绑定关系是否已被解析,也就是是否该对象被创建过;
  • $bindings: 记录 Interface A 的实现为 类 a,也就是数组 [A => a];
  • $methodBindings: 记录某类与某方法绑定起来,具体原理待深究;
  • $instances:记录某个对象,创建对象时直接将此对象返回;
  • $aliases:记录某个类及其别名,比如是["类 A" => "cache(类 A 的别名)"]
  • $abstractAliases: 记录某别名下有哪些类,举例来说是 ["cache" => ["redis 类", "memcache 类"]]
  • $extenders:将某类与某些闭包函数绑定起来,可以通过 getExtenders 方法获取这些闭包函数,具体原理待深究;
  • $tags: 将某些类打上tag,数据结构是["cache_tag" => ["redis 类", "memcache 类"]],可以批量操作,具体用法待深究;
  • $buildStack:记录所有已被创建的对象;
  • $with: 记录所有已被创建对象的参数;
  • $contextual:增加一个上下文绑定,具体原理待深究;
  • $reboundCallbacks:待深究
  • $globalResolvingCallbacks:待深究
  • $globalAfterResolvingCallbacks:待深究
  • $resolvingCallbacks;待深究
  • $afterResolvingCallbacks:待深究

部分调用关系:

resolved[]:
1. Container.resolved
2. Container.resolve
3. Container.flush
4. Container.offsetUnset

bindings[]:
1. Container.bind
2. Container.isShare
3. Container.getConcrete
4. Container.flush
5. Container.offsetUnset
6. Container.Bound
7. Container.getBindings

instances[]:
1. Application.make //只用于判断,然后延迟注册provider
2. Container.bound
3. Container.resolved
4. Container.isShared
5. Container.extend
6. Container.instance
7. Container.resolve
8. Container.dropStaleInstances
9. Container.forgetInstance
10. Container.forgetInstances
11. Container.flush
12. Container.offsetUnset

aliases[]:
1. Container.isAlias
2. Container.instance
3. Container.removeAbstractAlias
4. Container.alias
5. Container.getAlias
6. Container.dropStaleInstances
7. Container.flush

abstractAliases[]: 记录某类有哪些alias
methodBindings[]: 方法绑定
extenders[]: 扩展关系
tags[]: 给abstracts打tag

......

下述详细介绍的是bindings、intances以及aliases。

  • bindings
    这个可以称为类关系的绑定,也就是容器解析的时候所得到创建对象的方式,可以是类名或者是创建对象的闭包函数,根据该类名或函数可以创建出对应的对象。bindings可以分类以下几类:

    • 绑定自身,用代码展示更明了:
      $this->app->bind('App\Services\RedisEventPusher', null);
      
    • 绑定闭包
      $this->app->bind('HelpSpot\API', function ($app) {
        return new HelpSpot\API();
      });//闭包直接提供实现方式
      
    • 绑定接口
      $this->app->bind(
        'App\Contracts\EventPusher',  // Interface
        'App\Services\RedisEventPusher'   // 实现功能的具体类
      );
      
  • instances
    intances绑定是指的绑定已创建的对象,并不需要容器帮助创建对象,简单来说是:

    $api = new HelpSpot\API(new HttpClient);
    $this->app->instance('HelpSpot\Api', $api);
    
  • aliases
    别名的绑定,简言之就是维护一个KV对,K是别名,V是对象类名(或者反过来(aliases)),这个直接看源码更一目了然:

    foreach ([
          'db'                   => [\Illuminate\Database\DatabaseManager::class],
          'db.connection'        => [\Illuminate\Database\Connection::class, \Illuminate\Database\ConnectionInterface::class],
          'events'               => [\Illuminate\Events\Dispatcher::class, \Illuminate\Contracts\Events\Dispatcher::class],
          ......
      ] as $key => $aliases) {
          foreach ($aliases as $alias) {
              $this->alias($key, $alias);
          }
    }
    

    那$this->alias的实现呢?alias方法的实现非常简单,别名的解析就不在下述IoC执行过程里描述了,主要是维护aliases和abstractAliases两个别名关系,分别是两个php数组,一贴源码诸位便知>.<:

    public function alias($abstract, $alias)
    {
      ...
      $this->aliases[$alias] = $abstract;
      $this->abstractAliases[$abstract][] = $alias;
    }
    
解析及生产对象

就是使用make去创建对象。

public function make($abstract, array $parameters = [])
{
    // resolve 方法解析对象
    return $this->resolve($abstract, $parameters);
}

IoC容器的执行流程

上面讲述了laravel容器如果使用,也就是如何使用容器对应方法绑定类/对象,以及创建我们需要的对象。
下面我们看一看bind动作和make动作的具体实现过程。

bind动作过程
public function bind($abstract, $concrete = null, $shared = false)
    {
        $this->dropStaleInstances($abstract);

        if (is_null($concrete)) {
            $concrete = $abstract;
        }

        if (! $concrete instanceof Closure) {
            $concrete = $this->getClosure($abstract, $concrete);
        }

        $this->bindings[$abstract] = compact('concrete', 'shared');

        if ($this->resolved($abstract)) {
            $this->rebound($abstract);
        }
    }
make动作过程(resolve)
instance动作过程

这个文章算是烂尾了……有时间再补回来吧……

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,997评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,603评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,359评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,309评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,346评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,258评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,122评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,970评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,403评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,596评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,769评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,464评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,075评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,705评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,848评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,831评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,678评论 2 354

推荐阅读更多精彩内容