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)时,也是不可或缺的。 程序员

相关文章
相关标签/搜索