zoukankan      html  css  js  c++  java
  • 依赖注入

    个没有绝对答案的世界,却拥有绝对的丰富。 --《沈奇岚:我愿生命从容》

    2.11.1 定义

    (1) 关于依赖注入

    即控制反转,目的是了减少耦合性,简单来说就是使用开放式来获取需要的资源。

    (2) 关于资源

    这里说的资源主要是在开发过程中使用到的资源,包括配置项;数据库连接、Memcache、接口请求等系统级的服务;以及业务级使用到的实例等。

    引入依赖注入的目的不仅是为了增加一个类,而是为了更好的对资源进行初始化、管理和维护。下面将进行详细的说明。

    2.11.2 一个简单的例子

    很多时候,类之间会存在依赖、引用、委托的关系,如A依赖B时,可以这样使用:

    class A {
        protected $_b;
    
        public function __construct()
        {
            $this->b = new B();      
        }
    }

    这种方式在A内限制约束了B的实例对象,当改用B的子类或者改变B的构建方式时,A需要作出调整。这时可以通过依赖来改善这种关系:

    class A {
        protected $_b;
    
        public function __construct($b)
        {
            $this->b = $b;       
        }
    }

    再进一步,可以使用DI对B的对象进行管理:

    class A {
        public function __construct()
        {       
        }
    
        public function doSth()
        {
            //当你需要使用B时
            $b = $di->get('B');
        }
    }

    这样的好处?

    一方面,对于使用A的客户(指开发人员),不需要再添加一个B的成员变量,特别不是全部类的成员函数都需要使用B类服务时。另一方面在外部多次初始化A实例时,可以统一对B的构建。

    2.11.3 依赖注入的使用示例

    为方便使用,调用的方式有:set/get函数、魔法方法setX/getX、类变量$fdi->X、数组$fdi['X'],初始化的途径有:直接赋值、类名、匿名函数。

    /** ------------------ 创建与设置 ------------------ **/
    //获取DI
    $di = DI();
    //演示的key
    $key = 'demoKey';
    
    /** ------------------ 设置 ------------------ **/
    //可赋值的类型:直接赋值、类名赋值、匿名函数
    $di->set($key, 'Hello DI!');
    $di->set($key, 'Simple');
    $di->set($key, function(){
        return new Simple();
    });
    //设置途径:除了上面的set(),你还可以这样赋值
    $di->setDemoKey('Hello DI!');
    $di->demoKey = 'Hello DI!';
    $di['demoKey'] = 'Hello DI!';
    
    /** ------------------ 获取 ------------------ **/
    //你可以这样取值
    echo $di->get('demoKey'), "
    ";
    echo $di->getDemoKey(), "
    ";
    echo $di->demoKey, "
    ";
    echo $di['demoKey']. "
    ";
    
    /**
     * 演示类
     */
    class Simple
    {
        public function __construct()
        {
        }
    }

    2.11.4 依赖注入的好处

    (1)减少对各个类编写工厂方法以单例获取的开发量

    DI相当于一个容器,里面可以放置基本的变量,也可以放置某类服务,甚至是像文件句柄这些的资源。在这容器里面,各个被注册的资源只会存在一份,也就是当被注册的资源为一个实例对象时,其效果就等于单例模式。

    因此,保存在DI里面的类,不需要再编写获取单例的代码,直接通过DI获取即可。

    例如很多API的服务组件以及其他的一些类,都实现了单例获取的方式。分别如:

    微博接口调用:

    <?php
    class Weibo_Api
    {
        protected static $_instance = null;
    
        public static function getInstance()
        {
            if (!isset(self::$_instance)) {
                self::$_instance = new Weibo_Api();
            }
            return self::$_instance;
        }
    
        //....
    }

    七牛云存储接口调用:

    class Qiniu_Api {
        private static $_instance = null; //实例对象
    
        public static function getInstance()
        {
            if (self::$_instance ===null) {
                self::$_instance = new Qiniu_Api();
            }
            return self::$_instance;
        }
    }

    QQ开放平台接口调用:

    class QQ_Api { 
        private static $_instance = null; //实例对象
    
        public static function getInstance()
        {
            if (self::$_instance ===null) {
                self::$_instance = new QQ_Api();
            }
            return self::$_instance;
        }
    }

    如果使用DI对上面这些服务进行管理,则上面三个类乃至其他的类对于单例这块的代码都可以忽略不写。注册代码如下:

    $di->sStockApi = 'Weibo_Api';
    $di->sDioAopi = 'Qiniu_Api';
    $di->sShopApi = 'QQ_Api';

    上面是通过类名来进行延迟加载,但需要各个类提供public的无参数的构造函数。如果各个服务需要进行初始化,可以将初始化的工作放置在onInitialize()函数内,DI在对类实例化时会回调此函数进行初始化。

    (2)统一资源注册,便于后期维护管理

    这里引入DI,更多是为了“一处创建,多处使用”, 而不是各自创建,各自使用。

    创建和使用分离

    考虑以下场景:假设有这样的业务数据需要缓存机制,所以可注册一个实现缓存机制的实例:

    $di->set('cache', new FileCache());

    然后提供给多个客户端使用:

    $di['cache']->set('indexHtml', $indexContent);   //缓存页面
    $di['cache']->set('config', $config);  //缓存公共配置
    $di['cache']->set('artistList', $artistList);   //缓存数据

    当需要切换到MC或者Redis缓存或者多层缓存时,只需要修改对缓存机制的注入即可,如:

    $di->set('cache', new RedisCache());

    依赖注入的一个很大的优势就在于可以推迟决策,当需要用到某个对象时,才对其实例化。可以让开发人员在一开始时不必要关注过多的细节实现,同时也给后期的扩展和维护带来极大的方便。

    再上一层,假设未来我们需要更高级的缓存服务,那么我们可以在不影响客户端使用的情况下,轻松升级。
    未来的可配置化的多级缓存策略

    以下是一个模拟的使用场景,但依然对现在的项目有一定的帮助。假设我们现在有一个MC集群的缓存且引入了DI,使用如下:

    <?php
    
    //初始化
    $di = Core_DI::one();
    $di->cache = new Memcache();
    $di->cache->connect('localhost', 11211);
    
    //不同文件的多处使用 ...
    echo $di->cache->get('key');
    echo $di->cache->get('key2');
    echo $di->cache->get('key3');
    ...

    假设现在发现一层缓存存在穿透情况,为保证服务器的稳定性,我们已开发实现了多层缓存策略,并且可以通过简单配置即可实现,只需要对DI容器里面的cache实例进行升级,其他客户端的调用即可马上享受到缓存升级的优质服务。升级涉及改动的代码如下:

    <?php
    
    //初始化
    $di = new Core_DI();
    $di->cache = function () {
    $ultraFastFrontend = new DataFrontend(array(
        "lifetime" => 3600
    ));
    
    $fastFrontend = new DataFrontend(array(
        "lifetime" => 86400
    ));
    
    $slowFrontend = new DataFrontend(array(
        "lifetime" => 604800
    ));
    
    return new Multiple(array(
        new ApcCache($ultraFastFrontend, array(
            "prefix" => 'cache',
        )),
        new MemcacheCache($fastFrontend, array(
            "prefix" => 'cache',
            "host" => "localhost",
            "port" => "11211"
        )),
        new FileCache($slowFrontend, array(
            "prefix" => 'cache',
            "cacheDir" => "../app/cache/"
        ))
    ));
    };

    备注:关于多级缓存策略,后续会提供源代码和重用库,或者读者提供 (^_^)。

    (3)延迟式加载,提高性能

    延迟加载可以通过DI中的类名初始化、匿名函数和参数配置(未实现)三种方式来实现。

    延迟加载有时候是非常有必要的,如在初始化项目 的配置时,随着配置项的数据增加,服务器的性能也将逐渐受到影响,因为配置的内容可能是硬编码,可能来自于数据库,甚至需要通过接口从后台调用获取, 特别当很多配置项不需要使用时。而此时,支持延时加载将可以达到很好的优化,而不用担心在需要使用的时候忘记了初始化。从而很好的提高服务器性能,提高响 应速度。

    如对一些耗时的资源先进行匿名函数的初始化:

    $di['hightResource'] = function() {
        //获取返回耗性能的资源
        //return $resource; 
    }

    (4)以优雅的方式取代滥用的全局变量

    在我看来,PHP里面是不应该使用全局变量(global和$_GLOBALS),更不应该到处使用。

    用了DI来管理,即可这样注册:

    $di->set('debug', true);

    然后这样使用:

    $debug = $di->get('debug');

    也许有人会想:仅仅是换个地方存放变量而已吗?其实是换一种思想使用资源。

    以此延伸,DI还可用于改善优化另外两个地方:通过include文件途径对变量的使用和变量的多层传递。

    变量的多层传递,通俗来说就是漂洋过海的变量。

    2.11.5 DI思想的来源与推荐参考

    Dependency Injection/Service Location

  • 相关阅读:
    网线 ------ 交叉线
    ubuntu ------ 网络 ifconfig 不显示IP地址
    STM32L011D4 ----- 低功耗
    List 集合 使用 remove 踩得坑
    Map 集合遍历的4种方法
    elasticsearch 集群详解
    谷歌浏览器添加插件时显示程序包无效:"CRX_HEADER_INVALID" 解决办法
    MySql数据库 优化
    MySql 索引
    Kibana 安装
  • 原文地址:https://www.cnblogs.com/hellowzd/p/4670611.html
Copyright © 2011-2022 走看看