PHP 實踐 mix-in 概念 part 2 - MixableClass
php mix-in delegate ruby
第一部份《PHP 實踐 mix-in 概念之可行性》一文中解釋了 PHP 的個體如何加入新的方法。但那僅針對個體而非類別,那些混成內容無法繼承再用。而 Ruby 的混成(mix-in)概念是針對類別,其混成結果是一個類別,這些混成內容可經繼承機制再用。所以我接下來就要為 PHP 實踐一個可以混成的類別 - MixableClass。
我的設計目標有二。第一、個體可以動態增刪方法,且不影嚮其他個體。第二、以抽象化方法混成新的類別。
個體可以動態增刪方法
我可以將某類別的實例視為獨立個體,僅為此個體增添方法,而不經類別關係影嚮其他同類或衍生類別之個體。舉例而言,當我配置了個體 $x 之後,我可以只為 $x 增加 foo 方法。而其他個體不論是否與 $x 同類,皆不會自動具有 foo 方法。這一點在第一部份《PHP 實踐 mix-in 概念之可行性》已經實現了。
以抽象化方法混成新的類別
可用「與特定類別無關的抽象化方法」混成新的類別,且混成類別的特徵仍然要與一般類別相同。
- 混成基礎類別的方法可為衍生類別所繼承。
若我以 foo 方法混成了 MyClass 類,則繼承 MyClass 類之 MyClass2 類也會具有 foo 方法。
- 這些混成類別可以隨時增刪方法,且動態增刪之方法亦須依繼承原則運作。
若我將 foobar 方法動態加入 MyClass2 類,不僅所有已配置之 MyClass2 實例將立即具有 foobar 方法,連其衍生類別 MyClass3 之實例也將依繼承原則而具有 foobar 方法。
- 遵循子承父、父不承子之繼承原則。
當我動態加入 foobar 方法至 MyClass2 類後,衍生類別 MyClass3 將繼承 foobar 方法,但基礎類別 MyClass 不會繼承 foobar 。
在此有必要說明我所稱之「抽象化方法」為何?抽象化方法之意義與抽象類別 (abstract class) 或抽象方法 (abstract function) 不同。抽象方法與特定類別封裝在一起,且僅具函數簽名而不具有任何定義內容(沒有程式碼)。而我說的「抽象化方法」與特定類別無關但具有定義內容(有程式碼),可以將其視為純粹的演算法,是 Metaprogramming 中的一種概念。
混成類別: MixableClass.php
我設計了 MixableClass 實踐上述目標。在實作過程中,碰到了 PHP 動態能力不足之處,導致我必須多方嘗試並連帶影嚮實作結果的使用效能。這些狀況容我日後再提。
PHP 將方法混入個體或類別的方式,接近 C# 的委派方式,如同《Delegate in C# and Module in Ruby》所示。所以我以 delegate 表示混入方法。
MyClass_test.php
執行結果:
invoke $x->foo()
FOO Xman
invoke $y->foo()
FOO Yman
invoke $z->foo(), $z->bar()
FOO Zman
BAR
========== 動態混入/委派 =============
invoke $z->foobar("z")
ERR: fobar 尚未混入 MyClass3 ; MyClass3 尚未委派 foobar 行為.
foobar 混入 MyClass2 ; MyClass2 委派 foobar 行為.
invoke $z->foobar()
FOOBAR (z) Zman
子承父。MyClass2 的衍生類別 (MyClass3) 承繼其委派之 foobar.
invoke $y2->foobar()
FOOBAR (y2) Y2man
invoke $x->foobar()
ERR: 父不承子。MyClass2 的基礎類別 (MyClass) 仍無 foobar 行為.
========== 實例行為委派,不混入類別中 =============
invoke $x->myBar()
BAR
invoke $x2->myBar()
ERR: myBar 並未混入類別
invoke $y->myBar()
ERR: myBar 並未混入類別
上例之類別繼承關係如下圖所示。
各位不妨再看看「Prototype-based programming in PHP」以及「我也來實作 PHP mix-in 的概念」。他們的設計目標與我略有不同,可兩邊比較設計內容。
Posted by shirock at
樂多Roodo! │14:48
│
回應(9)
│
引用(1)
│
PHP
引用URL
http://cgi.blog.roodo.com/trackback/2884871
說明 石頭成老大把他心目中的 mix-in 目標做出來了,他主要的實作有以下兩個重點: 物件實體生成後
我也來實作 PHP mix-in 的概念 - Part 3【網站製作學習誌】
at 2007年03月27日 00:20
石頭成老大,看了你的程式後,我又想到一個有趣的實作方式,有時間我再來試看看。
不過我個人覺得上面的 delegate 方法如果改以接受
callback 虛擬型態的參數會不會比較 PHP 一點?類似
我也來實作 PHP mix-in 的概念 - Part 2裡的做法 (不過我倒是沒把一般函式考慮進去) 。
但是到現在還是沒想通 mix-in 在繼承以後能做些什麼事,只能感嘆自己實務經驗還不足,也許要再多看看 RoR 的設計概念了。
這牽涉到 PHP 的動態能力限制。
請看 MixableClass.php 的第31行,然後想想如何完成指派動作。
PHP 有三種呼叫方式: 一般函數、類別靜態方法、實例方法。基本上把第21行的 function_exists($fn) 改成 is_callable($fn) 就可以判定這是可呼叫的。但到第31行就麻煩了,如何儲存?
如果這三種方式都要考慮,這程式要增加不少程式碼。
現在的方式可以應付我的需求了,所以我目前只寫到這裡。更多內容要等到我有這需求時才會再去改了。
事實上,上面的作法無法同時兼顧效能與彈性。我覺得要改用
runkit extension 實現較佳。但 runkit 仍是新近加入的實驗性功能,還不是很穩固。
瞭解,如何儲存方法的確是 PHP 的弱項。沒有 closure 的支援, PHP 的動態性還是相當不足。這個問題有時間的話,我再來研究看看。
不過很我很有興趣的一點是,目前我所看過的 PHP 應用項目也很少使用這樣的方法;不曉得石頭成老大你所開發的項目是什麼,為什麼會有用到這種概念的想法?
抱歉,上面的 closure 本來要寫的是「核心語法」。
哈哈, 我都叫 closure 「封絕」或「閉鎖空間」的。XD
我目前有些功能直接用 Variable function 的方式,我在想如果能用 MixableClass 應該更方便。
目前還沒動手應用 MixableClass 於工作上。讓我想想要怎麼弄個實用的例子說明。
「封絕」...有點放武俠大絕的味道 XD
其實沒有實際用上也沒關係,我覺得這樣討論 PHP 可以做到什麼境界還滿有趣的。 :)
我已經想到一些可能的寫法了,這兩天有空來試試看。
看了mix-in這兩篇,第二篇覺得明確很多,這篇前段寫了目標,真的很棒。目標寫的好以及具體化,甚至比詳細的技術解決方案還重要,不過這是以應用來看就是了。
剛看的時候,其實還蠻不懂到底在玩什麼飛機,後來兩篇看完外加看了jaceju的那一篇和Prototype-based programming的解釋,才進入狀況,我實在是太蠢了。
「封絕」是一種自在式,可以實現一個與外部隔離的孤立空間,是紅世使徒... 停! 再講下去只有動漫狂聽得懂了。
其實像事件委派、序列化等動作都可以用混入的方式處理。例如,我要為一個類別加上可以將個體的公開內容序列化為 json 格式的方法時,就可以用混入而不是繼承方式處理。
例如:
User::delegate('User', 'jsonEncode', 'jsonEncode');
ProductOrder::delegate('ProductOrder', 'jsonEncode', 'jsonEncode');
Report::delegate('Report', 'json', 'jsonEncode');
這三個類別沒有親近的類別關係,若以繼承方式加上 json 序列化方法,可能要繼承好幾代。但以混成方式,只要直接委派就可以了。
另一種應用是和 Configuration-Driven Development 結合,自組態檔中讀出類別的schema後,即可動態混成新的類別,而不需要手動撰寫或產生程式碼。參見:
Example of Configuration Driven Development with PHP、
從 XML 產生 JSON 資料及方法的封裝。
嗯,看來應用還滿多的。以往如果要這樣實現的話,可能要多出好幾個類別。
也許還可以用在我目前發想的專案裡,這種方式實在是讓我學到了很多新想法 :)