PHP沉思錄-第二篇-PME模型-左輕侯-《程序員》2007年7月號

本文發表在《程序員》第7期 

PHP沉思錄之二 
左輕侯 
PME模型 

在大規模的程序設計中,組件(component)已經成爲一種很是流行的技術。常見的組件技術都基於PME模型,即屬性(Property)、方法(Method)和事件(Event)。基於PME的組件技術能夠方便地實現IoC(Inversion of Control,控制反轉),是從IDE的plugin到應用服務器的「熱發佈」等許多技術的基礎。 
PHP從版本5開始,大大完善了對OO的支持,之前不能被應用的許多pattern如今均可以在PHP5中實現。所以,是否可以實現基於PHP的組件技術,也就成了一個值得討論的問題。 
下面對PHP對於PME模型的支持,逐一進行討論: 

 屬性(Property) 
PHP並不支持相似Delphi或者C#的property語法,但這並非問題。Java也不支持property語法,可是經過getXXX()和setXXX()的命名約定,一樣能夠支持屬性。PHP也能夠經過這一方式來支持屬性。可是,PHP提供了另外一種也許更好的方法,那就是__set()和__get()方法。 
在PHP中,每個class都會自動繼承__set()和__get()方法。它們的定義以下: 

void __set ( string name, mixed value ) 
mixed __get ( string name ) 

這兩個方法將在下列狀況下被觸發:當程序訪問一個當前類沒有顯式定義的屬性時。在這個時候,被訪問的屬性名稱做爲參數被傳入相應的方法。任何類均可以重載__set()和__get()方法,以實現本身的功能。 
以下例: 

class PropertyTester { 

public function __get($PropName) { 
echo "Getting Property $PropName\n"; 


public function __set($PropName, $Value) { 
echo "Setting Property $PropName to '$Value'\n"; 



$Prop = new PropertyTester(); 
$Prop->Name; 
$Prop->Name = "some string"; 

類PropertyTester重載了__set()和__get()方法,爲了測試,僅僅將參數打印輸出,沒有作更多的工做。測試代碼建立了PropertyTester類的實例,並試圖讀寫它並不存在的一個屬性Name。此時,__set()和__get()相繼被調用,並打印出相關參數。它的輸出結果以下: 

Getting Property Name 
Setting Property Name to 'some string' 

基於這種機制,咱們能夠將屬性的值放在一個private的List中,在讀寫屬性時,經過重載__set()和__get()方法,讀寫List中的屬性值。 
可是,__set()和__get()方法的有趣之處遠不止及。經過這兩個方法,能夠實現動態屬性,也就是不在程序中顯式定義,而是在運行時動態生成的屬性。只要想一想這種技術在OR Mapping中的做用就可以明白它的重要性了。配合__call()方法(用於實現動態方法,在下一節中詳述),它可以取代醜陋的代碼生成器(code generator)的大部分功能。 

 方法(Method) 
PHP對方法的支持比較簡單,沒有太多能夠討論的。值得一提的是,PHP從版本5開始支持類的靜態方法(static method),這使得程序員不再用無謂地增長許多全局函數了。 

 事件(Event) 
事件也許是PHP遇到的最複雜的問題。PHP並無在語法層面提供對事件的支持,咱們只能考慮經過別的途徑來實現。所以,咱們須要先對事件的概念和其餘語言對事件的實現方式進行討論。 
事件模型能夠簡述以下:充當事件觸發者的代碼自己並不處理事件,而僅僅是在事件發生時,把程序控制權轉交給事件的處理者,在事件處理完成後,再收回控制權。事件觸發者自己並不知道事件將會被如何處理,在大多數狀況下,事件觸發者的代碼要先於事件處理者的代碼被完成。 
在傳統的面向過程的語言(例如C或者PASCAL)中,事件能夠經過函數指針來實現。具體來講,事件觸發者定義一個函數指針,這個函數指針能夠在之後被指向某個處理事件的函數。在事件發生時,調用該函數指針指向的處理函數,並將事件的上下文做爲參數傳入。處理完成後,控制權再回到事件觸發者。 
在面向對象的語言中,方法指針(指向某個類的方法的指針)取代了函數指針。以Delphi爲例,事件處理的例子以下: 

type 
TNotifyEvent = procedure(Sender: TObject) of object; 
TMainForm = class(TForm) 
procedure ButtonClick(Sender: TObject); 
… 
End; 
Var 
MainForm: TMainForm; 
OnClick: TNotifyEvent; 
… 

能夠看出,TNotifyEvent被定義爲所謂的過程類型(Procedural Type),事實上就是一個方法指針。TMainForm的ButtonClick方法是一個事件處理者,符合TNotifyEvent的簽名。OnClick是一個事件觸發者。在實際使用時,經過以下代碼: 

OnClick := MainForm.ButtonClick; 

將MainForm.ButtonClick方法綁定到了OnClick事件。當OnClick事件觸發時,MainForm.ButtonClick方法將被調用,而且將Sender(觸發事件的組件對象)做爲參數傳入。 
回到PHP,因爲PHP不支持指針,所以沒法使用函數指針這一技術。可是,PHP支持所謂的「函數變量」,能夠把函數賦予某個變量,其做用相似於函數指針。以下例: 

function EventHandler($Sender) { 
echo "Calling EventHandler(), arv = $Sender\n"; 


$Func = 'EventHandler'; 
$Func('Sender Name'); 

因爲PHP是一種動態語言,變量能夠爲任何類型,因此無須先定義函數指針的類型做爲事件的簽名。直接定義了一個函數EventHandler做爲事件處理者,而後將它賦予變量$Func(注意直接使用了字符串形式的函數名),最後觸發該事件,並將一個字符串「Sender Name」傳給它做爲參數。輸出的結果是: 

Calling EventHandler(), arv = Sender Name 

一樣地,PHP也提供了相似方法指針的機制。以下例: 

Class EventHandler { 

public function DoEvent($Sender) { 
echo "Calling EventHandler.DoEvent(), arg = $Sender\n"; 



$EventHanler = new EventHandler(); 
$HandlerObject = $EventHanler; 
$Method = 'DoEvent'; 
$HandlerObject->$Method('Sender Name'); 

因爲PHP中沒有可以直接引用對象方法的變量,所以須要使用兩個變量來間接實現:$HandlerObject指向對象,$Method指向對象方法。經過$HandlerObject->$Method方式的調用,能夠動態地指向任何對象方法。 
爲了使代碼更加優雅和更適合複用,能夠定義一個專門的類NotifyEvent,並使用一段新的調用代碼: 

final class NotifyEvent { 

private $HandlerObject; 
private $Method; 

public function __construct($HandlerObject, $Method) { 
$this->HandlerObject = $HandlerObject; 
$this->Method = $Method; 


public function Call($Sender) { 
$Method = $this->Method; 
$this->HandlerObject->$Method($Sender); 



$EventHanler = new EventHandler(); 
$NotifyEvent = new NotifyEvent($EventHanler, 'DoEvent'); 
$NotifyEvent->Call('Sender Name'); 

NotifyEvent類定義了兩個私有變量$HandlerObject和$Method,分別指向事件處理者對象和處理方法。在構造函數中對這兩個變量賦值,再經過Call方法來調用。 
熟悉C#的讀者能夠發現,NotifyEvent類與C#中的Delegate十分相似。Delegate超過NotifyEvent的地方在於支持多播(Multicast),也就是一個事件能夠綁定多個事件處理者。只要事件觸發者本身維護一個NotifyEvent對象數組,支持多播也不是一件難事。 
至此,PHP對事件的支持已經獲得了比較圓滿的解決。可是,人的求知慾是無窮無盡的。還有沒有可能經過其餘的方式來實現事件呢? 
除了方法指針,接口(interface)也能夠用於實現事件。在Java中,這種技術被普遍應用。其核心思想是,將事件處理者的處理函數定義抽象爲一個接口(至關於函數指針的簽名),事件觸發者針對這個接口編程,事件處理者則實現這個接口。這種方式的好處在於,不須要語言支持函數指針或方法指針,讓代碼顯得更加清晰和優雅,缺陷在於,實現同一種功能,要使用更多的代碼。以下例: 

interface IEventHandler { 
public function DoEvent($Sender, $Arg); 


class EventHanlerAdapter implements IEventHandler { 

public function DoEvent($Sender, $Arg) { 
echo "Calling EventHanlerAdapter.DoEvent(), Sender = $Sender, arg = $Arg\n"; 



class EventRaiser { 

private $EventHanlerVar; 

public function __construct($EventHanlerAdapter) { 
$this->EventHanlerVar = $EventHanlerAdapter; 


public function RaiseEvent() { 
if ($this->EventHanlerVar != null) { 
$this->EventHanlerVar->DoEvent($this, 'some string'); 



public function __tostring() { 
return 'Object EventRaier'; 




$EventHanlerAdapter = new EventHanlerAdapter(); 
$EventRaiser = new EventRaiser($EventHanlerAdapter); 
$EventRaiser->RaiseEvent(); 

首先定義了一個接口IEventHandler,它包含了方法的簽名。EventHanlerAdapter類做爲事件處理者,實現了這個接口,並提供了相應的處理方法。EventRaiser類做爲事件觸發者,針對$EventHanlerVar變量(它應該是IEventHandler接口類型,可是在PHP中不用顯式定義)編碼。在實際應用中,將EventHanlerAdapter的實例做爲參數賦予傳給EventRaiser類的構造函數,當事件發生時,相應的處理方法將被調用。輸出結果以下: 

Calling EventHanlerAdapter.DoEvent(), Sender = Object EventRaier, arg = some string 

最後,讓咱們回到現實世界中來。雖然咱們用PHP完整地實現了PME模型,可是這到底有什麼用呢?畢竟,咱們不會用PHP去編寫IDE,也不會用它編寫應用服務器。回答是,基於PME模型的組件技術能夠實現更加方便和更大規模的代碼複用。在基於PHP的應用系統中,雖然插件已經被普遍使用,可是經過組件技術能夠實現功能更強大、更加規範和更容易維護的插件。此外,組件技術在實現一些大的Framework(例如,針對Web UI的Framework)時,也是不可或缺的。 程序員

相關文章
相關標籤/搜索