php5 refactoring overloading magic_method
PHP5 在動態性及個體導向兩方面都做了大幅度的加強。其中 Magic methods 概念的引用,更為 PHP5 帶來許多靈活性。
本文說明如何活用 Magic methods 重整 (refactoring) 程式碼。讓 PHP 的程式碼更易於使用。主要重點在 __set(), __get()
,同時也示範了 __toString(), __isset(), __call()
的用途。
首先看一個 PHP4 語法的類別。See also: PHP Manual - Classes and Objects (PHP 4)
X.php in PHP4
第一步應該要先將它改成 PHP5 語法。See also: PHP Manual - Classes and Objects (PHP 5)
在重整過程中,除了語法的改寫外,也會改變私有資料成員 $_value1, $_value2 的結構,改以一個陣列放置所有私有屬性。此處先說明一下本文用語。本文通常稱呼用於表示個體內部資訊、狀態的資料項目為「屬性 (property)」。如果一個「屬性」直接指派給一個變數成員時,即 $this->value1 的形式,則本文稱這個「屬性」同時也是一個「資料成員 (data member)」。另一方面,也可用一個容器 (陣列) 作為「資料成員」,而將所有或大部份的「屬性」存放在這個「資料成員」的容器中。此時,本文就不稱呼這個「屬性」為一個「資料成員」。例如:陣列 $this->propertyMap 是一個「資料成員」,而 $this->propertyMap['value1'] 是一個「屬性」;在本文的用語中,此時的 value1 是一個屬性,但不是一個資料成員。底下是重整後的 X.php 。
X.php in PHP5
為何重整後改以關聯陣列的資料成員儲存私有屬性,而不直接用資料成員表示私有屬性呢?這跟 PHP 的「自識」能力 (或稱「反射」, Reflection) 有關。雖然關聯陣列和資料成員都可以動態增加內容,但受限於 PHP 的自識能力,使用關聯陣列儲存私有屬性較資料成員方便。因為使用資料成員的形式時無法以 foreach
走訪所有私有屬性。若用關聯陣列為容器存放屬性,便可以 foreach
走訪所有私有屬性。稍候的重整會顯示此用法的意義。
第二步,為了方便展示使用 magic methods 前後差異,自 X 類別衍生一個新類別 X2 ,在 X2 類別實作 magic methods 的內容。
X2.php
我們覆載 __set(), __get()
這兩個 magic methods 之後,表面上我們直接存取資料成員 $x2->value1, $x2->value2 ,但實際上根本沒有這兩個資料成員,而是藉由 __set()
去調用屬性 value1, value2 的專屬存取行為 $this->setValue1(), $this->setValue2()(X2.php 第19行)。我們用 method_exists() 判斷是否定義了專屬存取行為,用 變數函數 (variable function) 的形式調用。同樣地,雖然實際上沒有 $x2->value1, $x2->value2 資料成員,但表面上有,因此又覆載了 __isset()
以判斷是否設定了屬性 value1, value2 。最後可以看到 __toString()
的覆載作用。而在 __toString()
中也可以看出用關聯陣列存放屬性時,我們可以很方便地使用 foreach
走訪所有屬性。
接下來的重整目標是函數名稱的命名方式。其實在 PHP 中,我們不需用 setValue1()/getValue1()
的命名方式。利用預設參數就可以將 getter 和 setter 寫在一起,直接用 value1()
就可以表示兩者。而且用這種命名方式時, X2.php 第17行的 $method = 'set' . ucfirst($k);
之動作便可省略。 __set()/__get()
的處理效率會更好。
繼續自 X2 類別衍生一個新類別 X3 ,利用預設參數撰寫新的屬性存取行為。同時再利用 __call()
增加對函數名稱的相容性。
X3.php
X3.php 的第62行 (echo $x3->getValue3();
) 與第64行 ($x3->setValue3(2);
) ,都是透過 __call()
的覆載,才能調用到正確的 $x3->value3() (call_user_func_array())。不用擔心這會降低程式效能,因為 PHP5 只有在找不到明確定義的函數行為時,才會調用 __call()
。因此只要統一使用一致的函數名稱,便沒有機會調用 __call()
了。在本例中先後用了不一致的函數命名方式,才利用 __call()
容錯。
本文以繼承方式,利用 magic methods 逐一重整程式碼。實務上則不妨直接重整原有類別,無需繼承。透過 __set()/__get()
,除了將語法從函數調用修飾為成員存取,也提供我們幾乎不用增加任何函數行為就可以增加新的屬性,並以預設的方式保有對屬性的保護與控制之能力。應善用之。
相關文章
樂多舊網址: http://blog.roodo.com/rocksaying/archives/2683180.html