在开始之前先说明下,本例子仅仅是阐明观点,并没有考虑性能等其他方面的因素。

“货比三家”

用个简单的例子开始,有下面个数组:

$nums = array(10, 20, 30, 40);

需要找出数组中大于 15 的项。那么,不考虑闭包的情况下,我们或许会这样写:

  1. $res = array();  
  2. foreach ($nums as $n) {   
  3.    if ($n > 15) {        $res[] = $n;      
  4. }  

如果语言本身有闭包支持的,那么或许会这样写(Groovy 语言)

def res = nums.findAll { it > 15 }或者使用 Scala 语言

val res = nums filter (_ > 15)译注:Javascript 1.6 的话会是如下

var res = nums.filter(function(c){return c > 15});

因为循环操作已被抽象起来,所以可以看到 Groovy 、Scala (以及 Javascript) 都很漂亮得用一行就可以搞定。

当然,如果使用 PHP5.3 的闭包,也可以做到

$res = array_filter($nums, function($v) { return $v > 15; });

PHP 在这方面使用了比 Scala 更多的字符,但对比先前的例子,它更简短并且能更好得阅读。

顺便说下,上面的 PHP 代码实际上是使用了 Lambda 解析式,并不是个真正的闭包,这个 并不是我们目前关注的重点。详细阐述 PHP 闭包以及 Lambda 解析式的资料,可以参考这里。

目前看来感觉都还不错,那么我们再的题目增加点难度:找到所有大于 15 的项, 然后乘以 2 再加上作用域中的的某个变量值以后再返回。

Groovy 的实现:

  1. def x = 1def   
  2. res = nums .findAll { it > 15 } .collect { it * 2 + x } 

Scala 的实现:

  1. val x =   
  2. 1val res = nums filter (_ > 15) map (_ * 2 + x) 

PHP的实现:

  1. $x = 1;  
  2. $res = array_map(  
  3.     function($vuse ($x) {   
  4.         return $v * 2 + $x; },      
  5.      array_filter(        $nums,          
  6. function($v) { return $v > 15; })  
  7. );  

光从代码量方面,现在看起来 PHP 与其他语言有出入了。先抛开代码字面上本身 的审美不谈,上面的 PHP 代码还有个额外的问题。

例如,如果需要使用数组的键而非值作比较,怎么办?是的,上面的代码就办不到了。同时,从语法角度上说,上面的代码非常难以阅读。

返璞归真,这时还是得返回老土的思路去解决问题:

  1. $x = 1;  
  2. $res = array();  
  3. foreach ($nums as $n) {  
  4.     if ($n > 15) {  
  5.         $res[] = $n * 2 + $x;  
  6.     }  

这样看起来又很清楚了。但这个时候你或许又会迷惑了:“那还瞎折腾啥,这不就是个数组操作吗?”。

是的,好戏还在后头。这个时候该让 PHP 的某些高级特性出场,来搞定这看似有自残倾向 的“无聊问题”。

ArrayObject – 对数组的封装

PHP 有个称作 SPL 的标准库,其中包含了个叫做 ArrayObject 的类,它能提供“像数组一 样操作类”的功能,例如

  1. $res = new ArrayObject(array(10, 20, 30, 40));  
  2. foreach ($res as $v) {  
  3.     echo "$vn";  

ArrayObject 是个内置的类,所以你可以像其他类类操作一样封装它。

Arr - 包上糖衣

既然我们已经有了 ArrayObject 以及闭包这些特性,我们就可以开始尝试封装它:

  1. class Arr extends ArrayObject{      
  2. static function make($array)    {  
  3.         return new self($array);  
  4.     }    function map($func)  
  5.     {         
  6.  $res = new self();  
  7.         foreach ($this as $k => $v) {   
  8.            $res[$k] = $func($k$v);  
  9.         }        return $res;    }  
  10.     function filter($func)    {  
  11.         $res = new self();  
  12.         foreach ($this as $k => $v) {  
  13.             if ($func($k$v)) {   
  14.                $res[$k] = $v;  
  15.             }  
  16.         }  
  17.         return $res;  
  18.     }  

好了,万事俱备。下面重写的 PHP 代码就可以解决上面提到的问题,并且看起来语法上“差 不多”了:

$res = Arr::make($nums)    ->filter(function($k, $v)
{ return $v > 15; })    ->map(function($k, $v)
{ return $v * 2; });

上面的代码与传统方式有何不同呢?首先,它们可以递归并形成作用链式的调用,因此可以 添加更多的类似操作。

同时,可以通过回调的两个参数分别操作数组的键以及值其项 - $k 对应键以及 $v 对应值 。这使得我们可以在闭包中使用键值,这在传统的 PHP 函数 array_fliter 中是无法实现的。

另外个带来的额外好处就是更加一致 API 调用。使用传统的 PHP 函数操作,它们有可能第一个参数是个闭包,或者是个数组,抑或是多个数组…总之谁知道呢?

这里是 Arr 类的完整源代码,还包含了其他有用的函数(类似 reduce 以及 walk),其实它 们的实现其实方式和代码类似。

博弈

这个问题其实很难回答 - 这需要根据代码的上下文以及程序员自身等众多因素决定。其实 ,当我第一眼看见 PHP 的闭包实现时,我感觉似乎回到了那很久以前的 Java 时期,当时 我在开始使用匿名内置类(anonymous inner classes)来实现闭包。当然,这虽然可以做到, 但看起来实在是些画蛇添足。PHP 闭包本身是没错,只是它的实现以及语法让我感到非常的困惑。

其他具有闭包特性的语言,它们可以非常方便的调用闭包并同时具有优雅的语法。在上面的例子 中,在 Scala 中使用传统的循环也可以工作,但你会这样写吗?而从另个方面,那么有人 说上面这个题目使用 PHP 的闭包也可以实现,但一般情况下你会这样写吗?

可以确定,PHP 闭包在些情况下可以成为锐利的军刀(例如延时执行以及资源调用方面), 但在传统的迭代以及数组操作面前就显得有些为难。不要气馁不管怎么样, 返璞归真编写具有兼容性的、清爽的代码以及 API 是最重要的。

稳定

产品可用性高于99.9%

贴心

全国7*24小时客服热线

专业

产品经理在线技术支持

快速

快速上线运营快

承诺

我们选择声誉

坚持

10年专注高端品质开发
  • 返回顶部