Yii2設計模式——註冊樹模式

米粒人生發表於2018-12-20

應用舉例

在Yii.php中:

<?php

class ServiceLocator extends Component
{
    //儲存例項化的物件,每個物件都是單例,且有唯一string型別的ID做區分
    private $_components = [];

    //儲存設定的物件或者其定義,用於例項化
    private $_definitions = [];

    //將物件ID作為ServiceLocator的屬性,可通過$serviceLocator->{ID}直接獲取
    public function __get($name)
    {
        if ($this->has($name)) {
            return $this->get($name);
        }

        return parent::__get($name);
    }

    //檢驗是否有屬性$name
    public function __isset($name)
    {
        if ($this->has($name)) {
            return true;
        }

        return parent::__isset($name);
    }

    //檢驗是否有物件$id
    public function has($id, $checkInstance = false)
    {
        return $checkInstance ? isset($this->_components[$id]) : isset($this->_definitions[$id]);
    }

    //獲取一個物件$id
    public function get($id, $throwException = true)
    {
        //已經例項化的,直接返回
        if (isset($this->_components[$id])) {
            return $this->_components[$id];
        }

        //有該物件的定義,且定義已經是一個物件,設定$_components並直接返回
        if (isset($this->_definitions[$id])) {
            $definition = $this->_definitions[$id];
            if (is_object($definition) && !$definition instanceof Closure) {
                return $this->_components[$id] = $definition;
            }

            //有定義但不是現成物件,則交給DI Container去例項化,並且設定$_components
            return $this->_components[$id] = Yii::createObject($definition);
        } elseif ($throwException) {
            throw new InvalidConfigException("Unknown component ID: $id");
        }

        return null;
    }

    //設定、存放一個物件
    public function set($id, $definition)
    {
        unset($this->_components[$id]);

        if ($definition === null) {
            unset($this->_definitions[$id]);
            return;
        }

        //如果$definition是物件或者類名或者callable,則註冊到$_definitions中
        if (is_object($definition) || is_callable($definition, true)) {
            // an object, a class name, or a PHP callable
            $this->_definitions[$id] = $definition;
        } elseif (is_array($definition)) { //如果是帶`class`的配置陣列,也註冊到$_definitions中
            // a configuration array
            if (isset($definition[`class`])) {
                $this->_definitions[$id] = $definition;
            } else {
                throw new InvalidConfigException("The configuration for the "$id" component must contain a "class" element.");
            }
        } else {
            throw new InvalidConfigException("Unexpected configuration type for the "$id" component: " . gettype($definition));
        }
    }

    //清楚註冊的物件
    public function clear($id)
    {
        unset($this->_definitions[$id], $this->_components[$id]);
    }

}

這裡用到了註冊樹。

註冊樹模式

註冊樹模式(Registry Pattern)又叫註冊模式、註冊器模式。註冊樹模式通過將物件例項註冊到一棵全域性的物件樹上,需要的時候從物件樹上採摘的模式設計方法。

為什麼要採用註冊樹模式?

單例模式在整個專案中建立唯一例項的問題;工廠模式封裝了物件的建立方式(工廠方法——用一個抽象方法,抽象工廠——用一簇抽象方法),使得不必總用new關鍵詞去獲取物件;建立者模式則是分步驟的建立例項的各個部分;在Yii2中則通過依賴注入容器DI去獲取例項…

這些方法實際上都是解決一個問題——如何合理的產生一個物件。但物件既然已經產生出來了,怎麼方便的呼叫這些物件呢?我們在專案內部建立的物件好像散兵遊勇一樣,不方便統籌管理安排啊。因而註冊數模式應運而生。不管你是何種方式產生的物件,都給我“插到”註冊樹上。我用某個物件的時候,直接從註冊樹上去取一下就好了,是不是非常方便?註冊時模式還為其他模式提供了一種非常好的想法。

程式碼實現

看看註冊樹模式的實現:

class Register 
{
    //存放物件的陣列
    protected static $objects;
 
    /**
     * 存放一個物件
     * @param $alias
     * @param $object
     */
    public static function set($alias,$object)
    {
        self::$objects[$alias] = $object;
    }
 
    /**
     * 獲取一個物件
     * @param $alias
     * @return mixed
     */
    public static function get($alias)
    {
        return self::$objects[$alias];
    }
 
    /**
     * 銷燬一個物件
     * @param $alias
     */
    public static function _unset($alias)
    {
        unset(self::$objects[$alias]);
 
    }
}

註冊樹模式很類似服務定位器模式,優點是集中管理,使用方便。缺點是隱藏了物件和物件之間的依賴關係。

Yii2的註冊樹模式

PHP註冊樹模式主要用於建立物件的時候將我們的物件與相應的變數進行繫結,從這個角度上說,Yii2的Service Locator和DI Container都用到註冊樹模式。這二者都在內部維護一個陣列(key => value),value為物件或者物件定義,在獲取時通過唯一的key來獲取,如果是定義再去容器裡面例項化一下。

相關文章