PHP依赖注入和控制反转

什么是依赖

百度百科:依靠别人或事物而不能自立或自给称为依赖,在面向对象编程中,依赖指一个类依赖另一个类,我们用一段代码描述一下:

    class Cache{
        protected $client;
        public function __construct(MemcacheClient $client){
            $this->client = $client;
        }
    }
    class MemcacheClient {
        ···
    }
    $memcache = new MemcacheClient();
    $cache = new Cache($memcache);

从上段代码看到Cache类强依赖RedisClient类,这么看可能不那么容易理解,我们把代码修改一下

    use MemcacheClient;
    class Cache{
        protected $client;
        public function __construct(){
            $this->client = new MemcacheClient();
        }
    }

是不是很熟悉的感觉,RedisClient类被硬编码到Cache类中,即使到现在,我们很多的业务代码都是这么写的,突然有一天我们需要把我们的Cache驱动换到Redis,我们就需要修改Cache类

    use RedisClient;
    class Cache{
        protected $client;
        public function __construct(){
            $this->client = new RedisClient();
        }
    }

每次更换驱动,我们都需要进入Cache类进行修改,我们可以再优化一下

    interface CacheDriver{
    }
    class RedisDriver implements CacheDriver{
        // redis Driver
    }
    class MemcacheDriver implements CacheDriver{
        // memecache Driver
    }
    class Cache{
        protected $client;
        public function __construct(CacheDriver $client){
            $this->client = $client;
        }
    }
    $redis_driver = new RedisDriver();
    $memcache_drive = new MemcacheDriver();
    $cache = new Cache($redis_driver)
    $cache = new Cache($memcache_drive)

这样就不用每次都去修改Cache类就可以使用不同的驱动了!通过手动注入,将需要的驱动注入到Cache类中

小结

因为大多数应用程序都是由两个或者更多的类通过彼此合作来实现业务逻辑,这使得每个对象都需要获取与其合作的对象(也就是它所依赖的对象)的引用。如果这个获取过程要靠自身实现,那么将导致代码高度耦合并且难以维护和调试,所以才有了依赖注入的概念.

控制反转 IOC(Inversion Of Control)

百度百科:控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中

class Container
{
    protected $binds;

    protected $instances;

    public function bind($abstract, $concrete)
    {
        if ($concrete instanceof Closure) {
            $this->binds[$abstract] = $concrete;
        } else {
            $this->instances[$abstract] = $concrete;
        }
    }

    public function make($abstract, $parameters = [])
    {
        if (isset($this->instances[$abstract])) {
            return $this->instances[$abstract];
        }

        array_unshift($parameters, $this);

        return call_user_func_array($this->binds[$abstract], $parameters);
    }
}

开始注入容器

$container = new Container;
$container->bind('cache', function($container, $module_name) {
    return new Cache($container->make($module_name));
});
$container->bind('redis_driver', function($container) {
    return new RedisDriver();
});
$container->bind('memcache_driver', function($container) {
    return new MemcacheDriver();
});
$cache_redis = $container->make('cache', ['redis_driver']);
$cache_memcache = $container->make('cache', ['memcache_driver']);

假如我们要新加一个mongoDB的驱动,往容器中注册就可以了,Cache类不再主动的去获取RedisDriver/MemcacheDriver,而是被动等待,等待容器获取一个RedisDriver的实例,然后反向的注入到Cache类中,这就是控制反转。以上的例子还是不完美,因为每次都要我们手动注入到容器,容器自动注入需要用到PHP反射机制,这也是Laravel的核心机制