延續《分割程式功能以及 mix-in 和 include》的討論
延續《分割程式功能以及 mix-in 和 include》的討論。tokimeki 說: 既然已經在外部定義了function,直接調用function不就好了嗎?
我直接委派函數的原因是 PHP 語法限制。用函數比用靜態成員函數(類別方法)或一般成員函數(個體行為)要容易寫。請看我在另一篇的回應: 這牽涉到 PHP 的動態能力限制。。
至於不直接調用 function 這點,那是因為直接調用 function 的方式將損失動態性,或者說 "彈性"。如下例:
//--------
function foo() { echo 'foo'; }
function bar() { echo 'bar'; }
class A extends MixableClass {
}
class B {
function say() { echo 'BBB'; }
}
function allSay($a) {
foreach ($a as $o) {
$o->say();
}
}
//--------
$a = array(new A, new A, new B);
$a[0]->say = 'foo';
$a[1]->say = 'bar';
allSay($a);
/*
foo();
bar();
$a[2]->say();
*/
以這例子來說,我只需要知道我要調用 say 行為,其他都不用管了。如果直接調用函數,哪要如何寫?把 allSay($a); 這行改成沒有彈性的三行?
tokimeki又說: 我所需要的是「擴充」該類別的功能,換言之,他必須能像繼承一般,可以調用類別內protected的方法
。
先看繼承,我的 MixableClass 的設計目標就是要能繼承,這點並沒有什麼問題。至於說到擴充 protected method 的事,Ruby 可以做到這一點,但 PHP 不行。以 PHP 的動態能力來看,不允許你混入 protected 或 private method。
Ruby 可以 'open class' 後改變原有類別的任何方法 (包括 protected method)。如下例:
class A
def say
end
protected :say
def run
self.say
end
end
#open class A
class A
def say
print 'hello'
end
protected :say
end
a = A.new
a.run
第20行的 a.run
可以證明上例並不是定義一個新的 A 類別覆蓋先前的 A 類別。因為第2段 class A
的定義中,並沒有提到 run
這個行為。如果是覆蓋而非 'open' ,那麼就不會有 run
行為。
想學 Runy 這麼做嗎? PHP 會告訴你發生重覆定義類別的錯誤。
或許我們可以在 MixableClass 的 prototypeDelegate()
中多加一個指定存取屬性的參數,然後在 __call()
中排除嘗試調用註明 protected 的混入方法。不過 PHP 的動態能力不足以應付這種情形: 我們不知道是誰 call? 別忘了個體調用自己的行為時,也會透過 __call()
(當它原本沒有定義時)。
按 PHP 目前的能力來看,想要達成你的需求,要嘛用 include ,要不就用 hook 的技巧:你預先在方法定義中埋入 hook points ,然後你在外部插入 hooks 。
class A {
protected static $hooks = false;
function __construct() {
if ( !self::$hooks ) {
self::$hooks = array();
foreach ( get_class_methods(get_class($this)) as $methodName ) {
self::$hooks['pre_' . $methodName] = create_function('', 'return true;');
self::$hooks['post_' . $methodName] = create_function('', 'return true;');
echo $methodName, "\n";
}
}
}
protected function say() {
call_user_func(self::$hooks['pre_say']);
echo 'hello';
call_user_func(self::$hooks['post_say']);
}
public function run() {
return $this->say();
}
/*
...
其他省略,各位應該知道怎麼做了吧?
*/
}
$a = new A;
$a->run();
樂多舊回應