無意中看到了這個有趣的主題《取得 static method 實際上的 caller class type.》
雖不知為何有這樣的需求(是希望藉於 parent class 提供 static factory methods, 然後子類別呼叫 parent class's static factory methods ?? ), 這有個觀念上需要釐清, 但就結果論, 要得到這個結果的確是一個有趣的嘗試..
##CONTINUE##
Java static method 觀念:
static method(靜態方法) 不屬於物件實體,而是由類別名稱直接被存取的,所以它們不像實體方法一樣是在執行時選取的,也因此 static method 會被稱為「靜態」的原因,因為它們總是在編譯時就連結好了。
父類別中的 static method 可以被子類別中其它的 static method 所 shadow,但不能被 override。
但要是父類別的 method 被宣告成 final 的話,就代表它既無法被 shadow 也無法被 override。
一段關於 shadow 的名詞解釋 [1]:「使用和父類別中之變數相同的名稱來宣告變數,我們便可稱此一變數 shadow(遮蔽) 了父類別的變數。使用關鍵字 super 可以指向被遮蔽的父類別變數,或藉由將此物件轉型成父類別的型態來指向它」。
[1] Niemeyer & Knudsen,2001,Java 學習手冊,O'REILLY,第 6 章、附錄C。
相信在 PHP5 中的 static method 應該也是和 java 相同的定義.
所以這個問題的關鍵變成不在 this / self (php) / __CLASS__ (php) 在執行時期是誰.
是實作 static method class 本身, 所以我們得到的都會是 Parent 這個輸出. 換言中, 我們需要的是 呼叫的順序是什麼, 誰是第一個呼叫我? 就能可能嘗試推出想要的結果.
Java Way:
在 Java 中, 我們可以透過 Class.getName() 是取得 class name 的方式.
然而, 要使用 Class.getName() , 有幾個方式
- 物件實體(instance object).getClass().getName() 前題是要有物件的實體.
- Dog.class.getName() 然而, 您都寫 Dog.class 就表示你知道 Class Name = Dog .. orz.
- Class.forName("Dog").getName() 同上.
利用 Exception
還記每個 java programmer 的好朋友, 每每發生 Exception , 呼叫 e.printStackTrace() 時那 Dump 出來的資訊.
它記錄了所有 call stack trace. 所以利用 StackTrace 剛好可以得到這個問題的解決方式(算是偏門).
public class Parent {
public static void test() {
StackTraceElement[] stackTraceElement = new Throwable().getStackTrace();
System.out.println(stackTraceElement[stackTraceElement.length-1].getClassName());
}
public static void main(String[] args) {
Parent.test();
}
}
public class Child extends Parent {
public static void main(String[] args) {
Child.test();
}
}
public class AnotherChild extends Parent {
public static void main(String[] args) {
Child.test();
}
}
PHP Way:
PHP 中也可以呼叫 get_call_stack() 得到 call stack trace.
然而, 很不幸的 get_call_stack() 無法為 php 解決這個結果.
於是轉向 Hook static method 它. 利用 runkit 可以做到. (同 java: 算是偏門).
runkit 可於 PECL 下載 http://pecl.php.net/package/runkit , http://pecl4win.php.net/list_dlls.php
下面例子,我在 windows (只要丟個 php_runkit.dll , php.ini 加一加很快)測試通過.. 其它平台沒有測試.
<?php
class Father {
public static function getInstance($class=null) {
if ($class == null) $class = get_class();
echo "className = " . $class . "\n";
}
}
class Child extends Father{
}
class AnotherChild extends Father {
}
// Before runkit hook
echo Child::getInstance();
echo AnotherChild::getInstance();
class StaticMethodHelper {
public static function fixStaticMethod($parentClass) {
foreach (get_declared_classes() as $class) {
if (is_subclass_of($class, $parentClass)) {
$rc = new ReflectionClass($class);
foreach ($rc->getMethods() as $m) {
if ($m->isStatic() && $m->isPublic() && ($m->getDeclaringClass()->getName()!=$class) ) {
$body='$args = func_get_args(); array_unshift($args, "'.$class.'"); return call_user_func_array(array("parent","'.$m->getName().'"),$args);';
runkit_method_redefine($class, $m->getName(),'',$body, RUNKIT_ACC_PUBLIC);
}
}
}
}
}
}
// Runkit redefine static method.
StaticMethodHelper::fixStaticMethod('Father');
echo Child::getInstance();
echo AnotherChild::getInstance();
?>
Output:
C:\dev\php\php-5.2.3-Win32>php c:\tmp\test.php
className = Father
className = Father
className = Child
className = AnotherChild
以上僅就結果而言達到 "想要的效果結果", 但在 Java/PHP 實務上建議應該避免這樣的嘗試.
沒有留言:
張貼留言