(IoC)Ioc—Inversion of Control,即“控制反转”

在传统的编程设计中,我们都是在通过在代码里 new 来创建对象实例,代码中如果类与类之间相互依赖的关系越多,就会在代码中 new 越多的依赖对象实例,但是这样会让代码设计紧耦合,不利于后续程序的开发和维护。

// 定义书本接口规范
interface Book
{
    public function learn();
}

// 英文书
class EnglishBook implements Book
{
    public function learn(){
        echo 'learning...';
    }   
}

// 中文书
class ChineseBook implements Book
{
    public function learn(){
        echo '学习中...';
    }   
}

// 用户操作类
class User
{
    protected $book;

    public function __construct()
    {
        $this->book = new EnglishBook();   
    }

    public function learn()
    {
        //开始学习
        $this->book->learn();
    }

}

$user = new User();
$user->learn();

控制反转(Inversion of Control)是一种是面向对象编程中的一种设计思想,用来减低计算机代码之间的耦合度。其基本思想是:借助于“第三方”实现具有依赖关系的对象之间的解耦。这个所谓的 “第三方”,也就是IoC容器。

以上实例代码过于耦合,也不符合开放封闭原则,如果我们要让用户此时学习中文的话,就要重新改类里的代码,不符合我们对代码解耦和要求,我们基于控制反转的思想,对以上的代码进行改造。

我们将不在操作类中直接创建依赖对象的实例,而是把依赖对象的创建交给外部,把依赖对象创建的控制权由原先在操作类中直接创建,转换成操作类外部控制操作类应该使用哪一个依赖对象,并注入到操作类中去。

这就是控制反转的思想。

谁控制谁,控制什么:我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对 象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。

为何是反转,哪些方面反转了:传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。

// 定义书本接口规范
interface Book
{
    public function learn();
}

// 英文书
class EnglishBook implements Book
{
    public function learn(){
        echo 'learning...';
    }   
}

// 中文书
class ChineseBook implements Book
{
    public function learn(){
        echo '学习中...';
    }   
}

// 用户操作类
class User
{
    protected $book;

    public function __construct(Book $book)
    {
        $this->book = $book;   
    }

    public function learn()
    {
        //开始学习
        $this->book->learn();
    }

}

$user = new User(new ChineseBook());
$user->learn();

这样就不需要在User类中写死依赖的Book类,而是由第三方Ioc容器决定 将不同的Book注入到 User中去,User只需要使用这个传入的依赖就好了,实现了控制反转同时,这也同时涉及到了依赖注入,我们会在下段去理解依赖注入

DI—Dependency Injection,即“依赖注入”

控制反转和依赖注入是相辅而成的,理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:

  ●谁依赖于谁:当然是应用程序依赖于IoC容器

  ●为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源

  ●谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象

  ●注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)

我们通常会看到在一些框架中会经常用到这种设计,例如 Laravel , 相同的,想要理解现代化的开发框架设计思路,就要理解好IoC/DI。Laravel 代码实现例子:

// routes/web.php
Route::get('/post/store', 'PostController@store');

// App\Http\Controllers
class PostController extends Controller {

    public function store(Illuminate\Http\Request $request)
    {
        $this->validate($request, [
            'category_id' => 'required',
            'title' => 'required|max:255|min:4',
            'body' => 'required|min:6',
        ]);
    }

}

我们可以发现, Laravel 中使用依赖注入的时候,并不是像我们之前的例子,手动在外部创建依赖,而是直接定义好参数,在程序运行时就会自动实现依赖注入了,这种是如何实现的呢?

这里面其实用到了PHP 的 反射API,不懂的小伙伴可以查一下官方文档,学习一下。

反射的概念其实可以理解成根据类名返回该类的任何信息,比如该类有什么方法,参数,变量等等。我们先来学习下反射要用到的api。拿User举例

// 获取User的reflectionClass对象
$reflector = new reflectionClass(User::class);

// 拿到User的构造函数
$constructor = $reflector->getConstructor();

// 拿到User的构造函数的所有依赖参数
$dependencies = $constructor->getParameters();

// 创建user对象
$user = $reflector->newInstance();

// 创建user对象,需要传递参数的
$user = $reflector->newInstanceArgs($dependencies = []);

这时候我们可以创建一个make方法,传入User,利用反射机制拿到User的构造函数,进而得到构造函数的参数对象。用递归的方式创建参数依赖。最后调用newInstanceArgs方法生成User实例。 可能有些同学还不是很理解。下面我们用代码去简单模拟下

实现一个IoC容器

<?php
// 定义书本接口规范
interface Book
{
    public function learn();
}

// 英文书
class EnglishBook implements Book
{
    public function learn(){
        echo 'learning...';
    }
}

// 中文书
class ChineseBook implements Book
{
    public function learn(){
        echo '学习中...';
    }
}

// 用户操作类
class User
{
    protected $book;

    public function __construct(Book $book)
    {
        $this->book = $book;
    }

    public function learn()
    {
        //开始学习
        $this->book->learn();
    }

}

class Ioc
{
    public $binding = [];

    public function bind($abstract, $concrete)
    {
        //这里为什么要返回一个closure呢?因为bind的时候还不需要创建User对象,所以采用closure等make的时候再创建;
        $this->binding[$abstract]['concrete'] = function ($ioc) use ($concrete) {
            return $ioc->build($concrete);
        };

    }

    public function make($abstract)
    {
        // 根据key获取binding的值
        $concrete = $this->binding[$abstract]['concrete'];
        return $concrete($this);
    }

    // 创建对象
    public function build($concrete)
    {
        $reflector = new ReflectionClass($concrete);
        $constructor = $reflector->getConstructor();
        if(is_null($constructor)) {
            return $reflector->newInstance();
        }else {
            $dependencies = $constructor->getParameters();
            $instances = $this->getDependencies($dependencies);
            return $reflector->newInstanceArgs($instances);
        }
    }

    // 获取参数的依赖
    protected function getDependencies($paramters) {
        $dependencies = [];
        foreach ($paramters as $paramter) {
            $dependencies[] = $this->make($paramter->getClass()->name);
        }
        return $dependencies;
    }
}

//实例化IoC容器
$ioc = new Ioc();
$ioc->bind('Book',ChineseBook::class);
$ioc->bind('User',User::class);
$user = $ioc->make('User');
$user->learn();