控制反转,依赖注入,依赖查找,PHP语言描述

685 阅读3分钟

1. 依赖?

名词定义之类的就不罗嗦了,直接开搞!代码中经常遇到Class C里需要用到Class B,于是在Cnew B(),可能B里需要用到Class A,于是在Bnew A(),举一个常见的Mysql储存例子如下:

class Mysql{
    public function save(String $data){
        echo('save ['.$data.'] by Mysql'.PHP_EOL);
    }
}
class Storage{
    public function save(String $data){
        $Mysql = new Mysql();
        $Mysql->save($data);
    }
}
$obj = new Storage();
$obj->save('Tom is smart');//save [Tom is smart] by mysql

1.1 换了数据库

如果你只是写一个临时小脚本,这么写是没有问题的,但如果这是一个正经的项目,必然伴随着后续的升级迭代拓展优化,比如我们不用Mysql了,改用PostgreSQL,代码要新增PostgreSQL类,并修改Storage类如下:

class PostgreSQL{
    public function save(String $data){
        echo('save ['.$data.'] by PostgreSQL'.PHP_EOL);
    }
}
class Storage{
    public function save(String $data){
        $PostgreSQL = new PostgreSQL();
        $PostgreSQL->save($data);
    }
}
$obj = new Storage();
$obj->save('Tom is smart');//save [Tom is smart] by PostgreSQL

1.2 加了缓存层

比如流量变大了,增加了缓存层来加速查找,代码要新增Redis类,并修改Storage类如下:

class PostgreSQL{
    public function save(String $data){
        echo('save ['.$data.'] by PostgreSQL'.PHP_EOL);
    }
}
class RedisTest{
    public function save(String $data){
        echo('set cache ['.$data.'] by Redis'.PHP_EOL);
    }
}
class Storage{
    public function save(String $data){
        $PostgreSQL = new PostgreSQL();
        $PostgreSQL->save($data);
        $Redis      = new $RedisTest;
        $Redis->save($data);
    }
}
$obj = new Storage();
$obj->save('Tom is smart');
//save [Tom is smart] by PostgreSQL
//set cache [Tom is smart] by Redis

如果再有后续的功能,比如要把PostgreSQL换成Oracle,那就要新增Oracle类,并修改Storage;比如要把Redis换成MongoDB,同理操作,非常麻烦,其实主逻辑并不关心到底使用了什么数据库,什么缓存,只要能存下来就行了,而且我的修改应该是不改动主函数的代码的,不然相应的单元测试是不是也要改?这违背了设计模式的开闭原则,非常麻烦。

2. 控制反转

控制反转(英语:Inversion of Control,缩写为IoC),是面向对象编程面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup) ---维基百科

2.1 依赖注入

依赖注入的方式百科里有,这里不一一介绍,只举一个最常见的例子

class Storage{
    public $db;
    public $cache;
    public function __construct(PostgreSQL $db, RedisTest $cache)
    {
        $this->db    = $db;
        $this->cache = $cache;
    }
    public function save(String $data){
        $this->db->save($data);
        $this->cache->save($data);
    }
}
$obj = new Storage(new PostgreSQL(), new RedisTest());
$obj->save('Tom is smart');
$obj->save('Tom is smart');
//save [Tom is smart] by PostgreSQL
//set cache [Tom is smart] by Redis

这时候我们替换db时候已经不需要修改Storage类了,但如果替换db,代码仍然有修改,比如new PostgreSQL()改为 new Oracle()之类的,故而我们试一试将需要通过 new 实例化构造函数,或者通过工厂模式实例化的任务交给容器。

2.2 Ioc容器 依赖注入

// 声明一个'DB'接口
interface DB
{
    public function save(String $data);
}
class PostgreSQL implements DB{
    public function save(String $data){
        echo('save ['.$data.'] by PostgreSQL'.PHP_EOL);
    }
}
// 声明一个'Cache'接口
interface Cache
{
    public function save(String $data);
}
class RedisTest implements Cache{
    public function save(String $data){
        echo('set cache ['.$data.'] by Redis'.PHP_EOL);
    }
}

class Ioc{
    public $instances = [];

    public function register($alias, $class)
    {
        $this->instances[$alias] = $class;
    }
    public function make($alias)
    {
        return $this->instances[$alias] ?? false;
    }
}

class Storage{
    public $db;
    public $cache;
    public function __construct(DB $db, Cache $cache)
    {
        $this->db    = $db;
        $this->cache = $cache;
    }
    public function save(String $data){
        $this->db->save($data);
        $this->cache->save($data);
    }
}

$Ioc = new Ioc();
$Ioc->register('db',    new PostgreSQL());
$Ioc->register('cache', new RedisTest());

$obj = new Storage($Ioc->make('db'), $Ioc->make('cache'));
$obj->save('Tom is smart');
//save [Tom is smart] by PostgreSQL
//set cache [Tom is smart] by Redis

给类起了别名,主函数只负责调用,不用去关心细节具体是Mysql还是PostgreSQL,只要实现了DB接口就行。

2.3 Ioc容器 依赖查找

使用显式的依赖注入,要显式的往容器中注入实例化后的类,而使用PHP的反射机制,则可以实现自动注入。

// 声明一个'DB'接口
interface DB
{
    public function save(String $data);
}
class PostgreSQL implements DB{
    public function __construct(){}
    public function save(String $data){
        echo('save ['.$data.'] by PostgreSQL'.PHP_EOL);
    }
}
// 声明一个'Cache'接口
interface Cache
{
    public function save(String $data);
}
class RedisTest implements Cache{
    public function __construct(){}
    public function save(String $data){
        echo('set cache ['.$data.'] by Redis'.PHP_EOL);
    }
}

class Ioc{
    public $instances = [];
    
    public function getInstance($abstract){
        //获取类的反射信息,也就是类的所有信息
        $reflector = new \ReflectionClass($abstract);
        //获取反射类的构造函数信息
        $constructor = $reflector->getConstructor();
        //获取反射类的构造函数的参数
        $dependencies = $constructor->getParameters();
        if(!$dependencies){
            return new $abstract();
        }
        //一个类可能有多个依赖类
        foreach ($dependencies as $dependency){
            if(!is_null($dependency->getClass())){
                $p[] = $this->make($dependency->getClass()->name);
                //这里$p[0]是c的实例化对象,$p[1]是D的实例化对象
            }  
        }
        //创建一个类的新实例,给出的参数将传递到类的构造函数
        //构造出B的实例化对象,向上反推到A的实例化对象
        return $reflector->newInstanceArgs($p);
    }
    public function make($abstract){
        return $this->getInstance($abstract);
    }
}

class Storage{
    public $db;
    public $cache;
    public function __construct(PostgreSQL $db, RedisTest $cache)
    {
        $this->db    = $db;
        $this->cache = $cache;
    }
    public function save(String $data){
        $this->db->save($data);
        $this->cache->save($data);
    }
}

$Ioc = new Ioc();
$obj = $Ioc->make('Storage');
$obj->save('Tom is smart');
//save [Tom is smart] by PostgreSQL
//set cache [Tom is smart] by Redis