最近更新: 2007-03-07

PHP 實踐 mix-in 概念之可行性

最近在學習 Ruby 的過程中,接觸到'Mix-in' (混成) 這個新名詞。雖然是個新名詞,但其概念嚴格說來並非 Ruby 所獨有。

Mix-in (混成) 之基本概念在令行為抽象化,使其與特定類別或實例無關。我們再將這些抽象行為組成新的類別或個體。早期的模組化編程概念,其實就已經建立了這種概念, Ruby 則是聰明而有效結合模組與類別,增加許多設計彈性。

Ruby 可以這麼做,那其他語言呢? C/C++ 自然不用說,事實上, C/C++ 可以深入到系統層級實現任何程序員想做的事。說到這就扯遠了,我們當然希望在語言層級上實現混成概念。我想想, JavaScript 可以,那麼 PHP 呢?這篇文章的標題可還寫著 PHP ,當然要談。

說明 PHP 能否實踐混成(mix-in)概念之前,我們先來看 JavaScript 怎麼做?

function bar() {
    window.alert('bar: ' + this.name);
}

var o = {
    'name': 'abc'
}

o.foo = bar;
o.foo();

Invoking variable function without access object context

儘管 PHP 沒有函數指標、函數個體等功能,但藉著以「名」參照的方式,我們可以使用 Variable function 。自 PHP3 以來的長久時間中,我們一直藉 Variable function ,享受動態的函數調用技巧。那麼它能否實踐混成概念呢?首先我們先看一段基本程式。

<?php
function bar1() {
    echo "bar\n";
}

class O {
    private $name;
    public function __construct($name) {
        $this->name = $name;
    }
    public function __set($k, $v) {
        $this->$k = $v;
    }
    public function __call($f, $args) {
        $fp = $this->$f;
        $fp($this);
    }
}

$o = new O('abc');

$o->foo = 'bar1';
$foo = 'bar1';

$o->foo();
$foo();
?>

首先,我定義了一個獨立的函數bar1。接著定義一個類別O,其中以 __set 指派要混成行為的函數名稱,再以 __call 調用混成的行為。結果證實這是可行的,我們心裡可以先竊喜一下。

How about access object context

不要高興太早,函數bar1並沒有嘗試存取任何個體成員,它是一個完全獨立的函數。我們目前只證明它可以混成為類別行為。所以接下來我們要嘗試這樣的函數能否存取個體成員,就像開頭那個 JavaScript 程式所做的事一樣。

<?php
function bar1() {
    echo "bar\n";
}

function bar2() {
    echo 'bar: ', $this->name, "\n";
    // Fatal error: Using $this when not in object context.

}

function bar3(&$this = FALSE) {
    if ($this)
        echo 'bar: ', $this->name, "\n";
    // Fatal error: Using $this when not in object context.

}

function bar4(&$This = FALSE) {
    if (&$This)
        echo 'bar: ', $This->name;
        // Fatal error: Cannot access private property O::$name

    else
        echo "Not in an object context\n";
}

class O {
    private $name;
    public function __construct($name) {
        $this->name = $name;
    }
    public function __set($k, $v) {
        $this->$k = $v;
    }
    public function __call($f, $args) {
        $fp = $this->$f;
        $fp($this);
    }
}

$o = new O('abc');

$o->foo = 'bar4';
$foo = 'bar4';

$o->foo();
$foo();
?>

一拍兩瞪眼,廢話不多說。從bar2bar4 都發生錯誤。 PHP 的錯誤訊息很明顯地告訴我們,$this 關鍵字不能用在類別以外的函數中,這不像 JavaScript 的 this 會自動依情境指涉關聯個體。

儘管如此,函數bar4的錯誤訊息卻透露出了一絲絲曙光,它的訊息是不允許存取成員。這不難想像,畢竟我們宣告該成員為 private ,所以外部函數無法存取。但有 C/C++ 經驗的程序員這時應該聯想到夥伴函數(friend function) 了,如果我們宣告函數bar4 為類別的夥伴不就能存取了嗎?遺憾的是, PHP 不提供夥伴宣告功能,所以目前暫時做不到。

Access public member of instance

外部函數不能存取受保護的成員,又不能宣告為夥伴,那麼退一步想吧,只要是公開成員就可以做到了吧。

<?php
class O2 extends O {
    public $name;
    public function __construct($name) {
        $this->name = $name;
    }
}

$o2 = new O2('xyz');
$o2->foo = 'bar4';
$o2->foo();
?>

類別O2O類似,差別在其$name為公開成員。這一次終於可行了。

到目前為止,雖然 PHP 已經具有比 Java/C# 更良好的設計彈性,但作為一個動態語言還有許多地方需要改進。暫且先期待 PHP6 的成長吧。

相關文章
樂多舊網址: http://blog.roodo.com/rocksaying/archives/2817003.html

樂多舊回應
cedriccefc2002@gmail.com(泰迪熊的私藏蜂蜜) (#comment-21669735)
Fri, 18 Mar 2011 00:11:37 +0800
受了你文章的啟發,我寫了一個可以混成PHP命名空間的類別在:http://cedric0206.blogspot.com/2011/03/php-namespace-mix-in.html