Back

laravel服务容器

从根本弄不懂到逐渐理解...

laravel用了也挺久的了,曾经也想过深入研究读读源码什么的,但是尝试过几次都从心了。

正好寒假无聊得慌,然后就准备深入一点琢磨一下。

从服务容器开始(x 当初看laravel中文文档的时候,开头就是核心架构里面的服务容器什么的,把自己给怼自闭了

所以后来也没成功弄懂,后来倒是写过一次IoC的实例。但是到现在也忘光了(x

依赖注入和控制反转是对同一件事情的不同描述,它们描述的角度不同。

  • 依赖注入是从应用程序的角度在描述,应用程序依赖容器创建并注入它所需要的外部资源。
  • 控制反转是从容器的角度在描述,容器控制应用程序,由容器反向的向应用程序注入应用程序所需要的外部资源。

这个可能并不一定看的太懂,但是之所以要使用这一种模式关键还是为了解耦。


#0 常规操作

面向对象的程序开发离不开类、接口、对象这些东西。然后万物皆对象,房子可以是一个对象,人也是可以是一个对象,那么人在房子里。这两个对象就扯上关系了,他们之间就存在着一个依赖。

就用上面的例子,房子,人这个时候可能会这么写:

class Human {
    protected $name;
    protected $age;
    
    public function __construct($name,$age)
    {
        $this->name = $name;
        $this->age = $age;
    }
}

class House {
    protected $human;
    
    public function __construct()
    {
        $this->human = new Human('咕咕咕', 18);
    }
}

对于小型项目寥寥几个类这样的依赖并不会发生什么不愉悦的事情,就算业务发生变化有什么需要修改的事情也只需要修改寥寥几个地方。比如在上面的地方只有在House类的内部有一个new People

但是如果一个项目大了起来,类开始多了起来,然后依赖变得复杂起来。从一对一变成一对多甚至多对多的时候就是噩梦一般的体验了。比如:

class Human {
    protected $name;
    protected $age;
    
    public function __construct($name,$age)
    {
        $this->name = $name;
        $this->age = $age;
    }
}

class Dog {
    protected $name;
    protected $age;
    
    public function __construct($name,$age)
    {
        $this->name = $name;
        $this->age = $age;
    }
}

class Cat {
    protected $name;
    protected $age;
    
    public function __construct($name,$age)
    {
        $this->name = $name;
        $this->age = $age;
    }
}

class House {
    protected $human;
    protected $dog;
    protected $cat;
    
    public function __construct()
    {
        $this->human = new Human('咕咕咕', 18);
        $this->dog = new Dog('汪汪汪', 2);
        $this->cat = new Cat('喵喵喵', 2);
    }
}

#1 工厂模式 Factory Pattern

在上面的代码中,如果Cat类的构造函数,或者类名什么的发生了变化,那么就需要修改House的代码,可能发感觉不到什么,那么如果有其他的东西也对猫有依赖呢?比如有一个CatHouse类,有一个CatClimb类,或者更多?那么是不是意味着修改一个Cat一个构造函数就需要修改多个类?这将会是多么糟糕的体验?

所以为了避免这种事情,后来就有人提出了工厂模式,什么叫工厂模式呢?Show code。

class BiologyFactory
{
    public function make($type, $options)
    {
        switch ($type) {
            case 'human' : return new Human($options[0], $options[1]);
            case 'dog' : return new Dog($options[0], $options[1]);
            case 'cat' : return new Cat($options[0], $options[1]);
        }
    }
}

然后呢,房子类就可以这么写了:

class House {
    protected members;
    
    public function __construct($members)
    {
        $factory = new BiologyFactory;
        foreach($members as $member => $options){
            $this->members[] = $factory->make($member, $options);
        }
    }
}

试想,当我们这么操作之后,如果我们修改了Cat类,Dog类还需要修改House类的代码嘛,很明显是不需要的。又如同我们之前说的如果又多个类同时依赖Cat类,我们修改了Cat类的构造方法也再无必要去修改多个依赖了他的类,我们需要做的只有修改工厂类即可。

在这个过程中,我们相当于在一对多或者对多对的依赖中添加了一层工厂,原本类直接对类产生依赖,现在我们在类与类之间添加了一个工厂,让类依赖工厂,通过工厂来获取原来依赖的对象。这么一来是不是舒服很多了呢?

然而人们追求优雅的脚步是永无止尽的,既然可以做到像上面的类只需要依赖一个类,那么有没有可能做到完全的解耦呢?不管怎么修改我们都不需要去修改类的代码。

比如我们现在如果修改了Cat类,我们只需要修改工厂类的代码即可,那么有没有可能不需要修改工厂类的代码呢?再或者说,如果添加了更多的生物类我们就需要去修改工厂类的代码为switch添加更多的case。有没有可能不修改类的代码而是通过其他的方法来实现呢?

以及,这样的工厂真的就是最优雅的了吗?如果同一个生物有不同的构造方法,那么该怎么处理呢?是不是同一个类需要多个case来进行处理呢?这是不是意味着工厂生产东西的对象类型有些单一了呢?

所以,上述的问题都会被解决。

#2 依赖注入 Dependency Injection

类对类存在依赖,我们希望的是解耦,即修改一个类的代码不需要去改动其他类的代码,在前面的工厂模式中已经很接近完成这个目标了,无论怎么修改类的代码我们都只需要修改工厂类的代码即可完成适配。

类对类存在依赖,实际上就是在这个类里面我们会需要一个一个类的实例来干一些事情,那么除了在自己的内部产生还有没有别的办法来获得一个对象呢?有的,正如#2的标题所说,注入!

什么叫注入呢?只要不是在类的内部new或者工厂make的实例,而是通过参数或者其他形式获取的实例其实都叫注入。

比如:

class House{
    protected $memebrs
    
	public function __construct($members){
        $this->members = $members;
    }
}

这样是通过构造函数创建实例的时候就注入了一个已经创建好的对象,当然不可能需要我们手动去实例化对象,那和一开始#0的常规操作有什么区别呢?

前面也说过,依赖注入是从应用程序的角度在描述,应用程序依赖容器创建并注入它所需要的外部资源。

在初始化类的时候,依赖容器来实例化这一个House类,那么该如何让容器知道怎么实例化这个类呢?或者说再明确一点,怎么样让容器知道用什么参数来new一个House。我们可以规定只能传入一个固定的类的实例来构造我们的House,只需要这么修改构造函数:

public function __construct(Human $members){
    $this->members = $members;
}

但我们肯定不希望这个房子里只有人,

则会用到一个面向对象的语言都该有的概念——接口。接口是一种约束形式,其中只包括成员定义,不包含成员实现的内容。