2011年10月14日星期五

PHP Closure的一个应用

Closure是一种很灵活的技术,如果运用得当,可以大大提高代码的可维护性,尤其对于象PHP这样的动态语言更是如此。Closure的定义可以在Wikipedia上查到,关于这一技术的学术文章也可以轻易的搜索到,在此就不赘述了。

在工作中,我需要实现一个事件处理系统。我把它设计为一个类,在类的一个方法中,我设计了一个回调函数来处理事件。它的主要逻辑如下:
  1. //main.php
  2. require 'event_handlers/index.php';
  3. $type = 'event1';
  4. function handleEvent() {
  5.     global $type;
  6.     if (function_exists('handle_'.$type)) {
  7.         call_user_func('handle_'.$type);
  8.     }
  9. }
  10.  
  11. //event_handlers/index.php
  12. require 'event1.php';
  13.  
  14. //event1.php
  15. function handle_event1() {...}
在上面的例子中,我使用了一个全局的变量$type,这种做法只是为了少打一些字,突出主要逻辑,在实际代码中handleEvent是一个类的方法。

它的结构很简单,主程序中引用了一个存放所有事件的处理函数的“索引”文件,即event_handlers/index.php,每个种类的事件处理程序都被放置在一个独立的文件中,由索引文件引用。

现在问题来了。由于系统设计中的不确定性,我不能保证事件类型(即$type)将来不会改名。一旦类型的名字改了,就需要改变每个类型处理函数的名字,而一 类事件会有多个不同的处理函数(即上例中event1.php中的函数个数会有多个)。为了使将来的代码维护尽量简单,我从#php学了一个新的方法,即 利用closure使得事件处理函数的名字是动态的。一旦发生事件类型改变,只需要修改事件处理“函数库”的文件名即可。下面的例子演示了这种用法:

//main.php
$event_type = $argv[1];
require_once "$event_type.php";
$handler = 'handle_'.$event_type;
if (is_callable($$handler)) {
    call_user_func($$handler);
} else {
    echo "$handler is not defined\n";
}

//event1.php
$event_type = basename(__FILE__, '.php');
$handle_this = 'handle_'.$event_type;
$$handle_this = function() {
    global $event_type;
    echo "Testing handler of $event_type...\n";
};

在 这个例子中,event1.php可以任意重命名,主程序只需要以那个名字调用它即可以引用其中定义的事件处理函数。需要注意的是这里必须用 is_callable代替function_exists,因为Closure从本质上说是匿名函数,不能用function_exists检测其存在 与否。另外,两次解引用的方法(即$$handler、$$handle_this等)也需要注意不要弄错了。

没有评论: