通过php代码去了解IOC/DI依赖注入和控制反转

小文blog小文 2018-07-30 15:40 864人围观

IOC容器

class IOC{
    /**
     * 绑定 全局单例对象
     * @var array   ['class_name' => obj]
     */
    private static $single   = [];
    /**
     * 绑定 非单例对象
     * @var array  ['class_name' => class_name]
     */
    private static $nosingle = [];
    /**
     * 注册全局单例对象
     * @param $name      class_name 类名::class
     * @param $obj       new class_name  obj
     * @throws Exception
     */
    public static function singleton($name, $obj){
        if (!is_object($obj)) {
            throw new Exception('method singleton args $obj must be object');
        }
        static::$single[$name] = $obj;
    }
    /**
     * 注册非单例对象,运行时候需要参数自动实例化
     * 这里name可能是顶层接口,也有可能是类名
     * @param $name             name 类名::class
     * @param $class_name       class_name 类名::class
     */
    public static function nosingleton($name, $class_name){
        static::$nosingle[$name] = $class_name;
    }
    /**
     * 通过IOC容器获取对象实例
     * @param $name   class::name
     * @return object
     * @throws ReflectionException
     */
    public static function getInstance($name) {
        //用反射查看该类实例化需要的参数,也就是构造函数参数
        $param = static::getParams($name);
        return $param['param'] ? (new ReflectionClass($name))->newInstanceArgs($param['param']) : (new ReflectionClass($name))->newInstance();
    }
    /**
     * 获取参数
     * @param $name           类名
     * @param string $method  方法
     * @return mixed
     * @throws ReflectionException
     */
    public static function getParams($name, $method = '__construct') {
        $param['param']  = [];  //参数
        $param['value']  = [];  //默认值
        //如果没有构造函数 则返回空
        if ($method == '__construct') {
            $rf  = new ReflectionClass($name);
            if (!$rf->hasMethod($method)) {
                return $param;
            }
        }
        //反射方法 获取参数
        $rf_method = new ReflectionMethod($name, $method);
        $params = $rf_method->getParameters();
        if (!empty($params)) {
            foreach ($params as $k => $v) {
                if ($v_class = $v->getClass()) {
                    //如果这个参数是对象,则获取对象类名,查看容器内是否有对应的设置
                    $v_class_name = $v_class->getName();
                    if (array_key_exists($v_class_name, static::$single)) {
                        //单例对象 直接返回
                        $param['param'][] = static::$single[$v_class_name];
                    } elseif (array_key_exists($v_class_name, static::$nosingle)) {
                        //非单例对象,通过name去实例化对象
                        $param['param'][] = static::getInstance(static::$nosingle[$v_class_name]);
                    } else {
                        //容器内没有这个,直接调用getInstance
                        $param['param'][] = static::getInstance($v_class_name);
                    }
                } else {
                    //非对象
                    $param_name = $v->getName();
                    if ($v->isDefaultValueAvailable()) {
                        // 是否包含默认值
                        $param_default_value = $v->getDefaultValue();
                        $param['value'][$param_name] = $param_default_value;
                    }
                    $param['param'][] = $param_name;
                }
            }
        }
        return $param;
    }
    /**
     * 容器的运行入口 主要负责加载类方法,并将运行所需的标量参数做映射和默认值处理
     * @param       $name     类名
     * @param       $method   方法
     * @param array $params   参数
     * @return mixed
     * @throws ReflectionException
     */
    public static function run($name, $method, array $params = array())
    {
        if (!class_exists($name)) {
            throw new Exception($name . "not found!");
        }
        if (!method_exists($name, $method)) {
            throw new Exception($name . "::" . $method . " not found!");
        }
        //调用getInstance 通过ioc容器获取类对象
        $instance = static::getInstance($name);
        //获取此方法需要的参数
        $param    = static::getParams($name, $method);
        //通过param  和 传入的$params  合并 方法运行参数
        $_param = array_map(function ($v) use ($param, $params) {
            // 对象参数
            if (is_object($v)) {
                return $v;
            }
            // 传入的运行参数 有对应的设置
            if (array_key_exists($v, $params)) {
                return $params[$v];
            }
            // 使用默认值
            if (array_key_exists($v, $param['value'])) {
                return $param['value'][$v];
            }
            // 没有匹配到任何值 报出异常
            throw new Exception($v . ' args error');
        }, $param['param']);
        // 运行
        return call_user_func_array([$instance, $method], $_param);
    }
}

相关依赖

// 它将被以单例模式注入 全局的所有注入点都使用的同一实例
class singleton
{
    public $name = 'singleton';
}
// 它将以普通依赖模式注入 各注入点会分别获取一个实例
class noSingleton
{
    public $name = 'noSingleton';
}
// 接口注入
interface Animal
{
    public function fly();
}
// 接口实现
class Cat implements Animal
{
    public function fly() {
        echo 'Cat fly';
    }
}
// 接口实现
class Dog implements Animal
{
    public function fly() {
        echo 'Dog fly';
    }
}

测试运行

// 具体的运行类
class TestController
{
    public $singleton;
    public $noSingleton;
    // 这里自动注入一次
    public function __construct(singleton $singleton, noSingleton $noSingleton)
    {
        $this->singleton   = $singleton;
        $this->noSingleton = $noSingleton;
    }
    // 这里的 singleton 和 noSingleton 和 Animal 会再次注入一次
    public function show(singleton $singleton1, singleton $singleton2, noSingleton $noSingleton1, noSingleton $noSingleton2,$name, Animal $animal, $age = 25) {
        //是否单例传入
        var_dump($singleton1 === $singleton2);
        echo "<br>";
        //是否非单例传入
        var_dump($noSingleton1 === $noSingleton2);
        echo "<br>";
        //是否实现接口
        $animal->fly();
        echo "<br>";
        //其他标量参数
        var_dump($name);
        echo "<br>";
        var_dump($age);
        echo "<br>";
    }
}
try{
    // 依赖的单例注册
    IOC::singleton(singleton::class, new singleton());
    // 依赖的非单例注册
    IOC::nosingleton(noSingleton::class, noSingleton::class);
    // 接口的注册 Animal 注册关联具体的实现类 cat dog 这里绑定一只猫
    IOC::nosingleton(Animal::class, Cat::class);
    // 运行
    IOC::run(TestController::class, 'show', ['name' => '小文blog']);
} catch (Exception $e) {
    echo $e->getMessage();
}
//bool(true)
//bool(false)
//Cat fly
//string(10) "小文blog"
//int(25)

思考

这么麻烦干嘛,我为何不能需要什么对象就new一下

当然不能,ioc/di最大好处就是解耦,比如  我show方法 的第一个参数需要改变成另一个对象,那我直接修改依赖即可,如果我的animal的想让dog fly 那我注册的时候 就把dog注册上即可, 类里面不需要任何改变

这个就是所谓的面向接口编程,接口可以理解为一个规范、一个约束。高层模块不直接依赖于低层模块,它们都应该依赖于抽象(指接口)。

总结IOC/DI

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

依赖注入是个花俏的名词,事实上是指:类的依赖通过构造器或在某些情况下通过「setter」方法「注入」

使用依赖注入,最重要的一点好处就是有效的分离了对象和它所需要的外部资源,使得它们松散耦合,有利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。

其实DI和IOC说的是一个事情,IOC就是要控制反转,而DI是实现IOC的方式!

转载请注明来自小文blog,本文标题:通过php代码去了解IOC/DI依赖注入和控制反转

发布评论
生活是一场戏,主角当累了,你亦可成为观众,停下脚步,歇一歇