iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 6
1
Software Development

PHP 大師之路 - 開源的技術淬練系列 第 6

Day 6 - PHP 設計模式:註冊表 (Registry) + 單例 (Singleton)

  • 分享至 

  • xImage
  •  

Day 4Day 5 筆者分別介紹了單例模式以及註冊者模式。把 Day 4 的「單例特性」範例程式碼改寫成如下:

trait Singleton
{
   /**
    * Constructor
    */
    private function __construct()
    {

    }

    /**
     * 取得實例
     * 
     * @return self
     */
    public static function getInstance()
    {
        $name = strtolower(get_called_class());
        $name = ltrim(substr($name, strrpos($name, '\\')), '\\');

        if (!Registry::has($name)) {
            $instance = new self();
            Registry::set($instance, $name);
        }

        return Registry::get($name);
    }
}
  1. 因為使用註冊表來存放單例,instance 靜態屬性不再需要,所以移除了。
  2. getInstance 方法改寫成使用註冊表來返回單例。

說明

$name = strtolower(get_called_class());

取得類別名稱 (含命名空間的名稱)。

get_called_class() 是用來取得「延遲靜態綁定」 (late static binding) 的類別名稱,專門用在靜態方法裡取得正確的類別名稱,如果是放在非靜態的方法中,作用和 get_class() 無異。

$name = ltrim(substr($name, strrpos($name, '\\')), '\\');

去除命名空間的名稱,取得類別本身的名稱。
例:APP\CORE\MAN 過濾後取得字串 man

if (!Registry::has($name)) {
    $instance = new self();
    Registry::set($instance, $name);
}

註冊表如果不存在 Man,則實例化 Man 之後存入註冊表。

return Registry::get($name);

從註冊表取出 Man 的實例。

如果是用這樣子的方式,則取用 Man 這個類別的單例則變成既可以由本身的 getInstance 靜態方法取得,也可以從註冊表取得。

$a = Man::getInstance();
$b = Registry::get('man');

$a$b 為相同的實例。

結論

這個範例是藉由註冊表模式本身具存放物件的唯一實例的這種性質,搭配單例模式,剛好可以混合使用。不過你會發現,getInstance 內容變複雜了。程式碼沒變精簡反而變更多,筆者舉這個範例的理由很簡單:

「設計模式常常是混合使用,是活的而不是死的。是彈性的,而非死守的信條。」

下一篇文章,筆者要為大家介紹的是最常見的「工廠模式」(factory pattern)。


本文同步更新於 TerryL 部落格 Day 6 - PHP 設計模式:註冊表 (Registry) + 單例 (Singleton),歡迎前往討論。


上一篇
Day 5 - PHP 設計模式:註冊表 (Registry)
下一篇
Day 7 - PHP 設計模式:工廠 (Factory)
系列文
PHP 大師之路 - 開源的技術淬練30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
Oberon Lai
iT邦新手 5 級 ‧ 2022-07-28 23:15:28

Terry 大大

正在研究 WooCommerce Store API,發現到它就是用註冊表模式來放 Route,但裡面有個用法我不太明白:它把類別註冊到容器裡是用 SomeClass::class,查到說這樣寫就是指到類別本身,想請教這樣是不是就不用像本文中還需要用 get_called_class() 跟去除 namespace 的處理?

附上參考資料:https://stackoverflow.com/questions/30770148/what-is-class-in-php

Terry L. iT邦研究生 4 級 ‧ 2022-07-29 09:35:45 檢舉

週末來看一下 Store API 研究一下 ^^

它的 Cotainer 是這樣註冊的:

$container->register(
    RoutesController::class,
    function ( $container ) {
        return new RoutesController(
            $container->get( SchemaController::class )
        );
    }
);

然後取得已註冊的物件:

$container->get( RoutesController::class )

Container 的 register() 方法是這樣寫:

public function register( $id, $value ) {
    if ( empty( $this->registry[ $id ] ) ) {
        if ( ! $value instanceof FactoryType ) {
            $value = new SharedType( $value );
        }
        $this->registry[ $id ] = $value;
    }
}

看起來用 ::class 寫法的好處是可以把整個 namespace 一起吃進去,之後要替換 Container 就很方便~

Terry L. iT邦研究生 4 級 ‧ 2022-07-29 13:44:01 檢舉

是的。是 registry pattern。它的寫法更靈活一點,在取用的時候才 new 物件。也可以在匿名函式中下一些判斷式決定要 new 那一個物件。另外在 runtime 的時候才 new 物件也是比較省記憶體的寫法。

我要留言

立即登入留言