PHP V5和面向对象编程
2004年PHP V5发布,和PHP V4相比,它在面向对象编程(OOP)和设计方面有了巨大的进步,增加了一些必要的特性,如类的可见性、正确的构造函数与析构函数、类型提示和类反射API。PHP5为高级面向对象编程开启了方便之门,使得开发者可以更简单地实现各种设计模式,设计出更好的类和API。
在PHP V5.3中,继续大幅加强OOP能力,这些改进包括语法的进化和性能的改善等方面。首先让我们来看静态方法和属性相关的新特性。
改进后的静态方法与属性的处理方式
PHP V5中一个实用的新增特性是可以指定类的一个方法或属性为静态成员(PHP V4支持静态地访问类的方法和属性,但是无法指定它们是为静态访问而设计的)。静态访问在实现单例模式时非常有用,此时,类只存在一个实例。
PHP V5.3增加了若干特性,以加强对类的静态方法和属性的支持。首先,我们来分析新增的魔术方法:__callStatic()。
__callStatic()魔术方法
PHP V5包含了若干个可以用于类内部的魔术方法。如果在类中定义了这些方法,它们可以提供特殊的功能,从而实现重载(允许一个方法接收不同类型的参数)和多态(允许不同数据类型使用相同的接口)功能。同时,它们也让PHP中的面向对象编程(OOP)和设计模式的实现变得更加简单。
在PHP V5.3中,新增了一个魔术方法:__callStatic()。它和__call()魔术方法类似,后者用于处理类中不存在或不可见的方法调用。而__callStatic()用于处理类似的静态方法调用,方便开发者实现方法的重载。下面是一个如何使用的例子。
Listing 1. Example of using __callStatic() vs. __call()
class Foo
{
public static function __callStatic(
$name,
$args
)
{
echo "Called method $name statically";
}
public function __call(
$name,
$args
)
{
echo "Called method $name";
}
}
Foo::dog(); // 输出"Called method dog statically"
$foo = new Foo;
$foo->dog(); // 输出"Called method dog"
|
需要注意,PHP要求
__callStatic()方法必须是public,而且是static。类似的,
__call()魔术方法也要求定义为public,其实所有的魔术方法都要求定义为public。
动态的静态调用
PHP中有一个实用的特性是变量的变量。它意味着你可以用一个变量的字符串值作为另外一个变量的名字。换句话说,你可以完成下面的功能。
Listing 2. Variable variables
x = 'y';
$$x = 'z';
echo $x; // 输出'y'
echo $$x; // 输出'z'
|
同样的概念可以用于函数,甚至类的成员函数,如下例所示。
Listing 3. Variable function and class method names
class Dog
{
public function bark()
{
echo "Woof!";
}
}
$class = 'Dog'
$action = 'bark';
$x = new $class(); // 实例化类'Dog'
$x->$action(); // 输出"Woof!"
|
在PHP V5.3中增加了一个功能,静态调用中的类名可以通过一个变量来指定。由此引入了一些新的编程方法,如下所示。
Listing 4. Variable class naming
class Dog
{
public static function bark()
{
echo "Woof!";
}
}
$class = 'Dog';
$action = 'bark';
$class::$action(); //输出"Woof!"
|
这个新增功能使得PHP中变量的变量这个特性成为一个完备特性,可用于各种环境。
接下来让我们关注另外一个更加实用的关于静态方法和属性方面的功能改善:静态调用的晚绑定。
静态调用的晚绑定
PHP V5.3之前一个令人恼火的事情是静态方法和属性的处理。到目前为止,静态引用,包括使用self或__CLASS__,都是在函数定义所在类的范围内解析。当继承一个类,并在子类中调用静态函数时,将会发生解析错误。PHP V5.3中新增的静态调用晚绑定特性解决了这个问题。为了更好的说明这个问题,我们创建如下一个包含静态方法的类。
Listing 5. Class Foo with static method test()
class Foo
{
protected static $name = 'Foo';
public static function test()
{
return self::$name;
}
}
|
然后继承这个类,并在子类中重定义$name属性。
Listing 6. Child class Bar that extends parent class Foo
class Bar extends Foo
{
protected static $name = 'Bar';
}
|
最后进行一个静态调用。
Listing 7. Static method call test()
这个调用的输出将是字符串
Foo。因为test()方法中的self::$name引用是在 Foo这个类中解析。而PHP解释器这么做是因为test()方法是在类Foo中定义的。
PHP V5.3通过使用static关键字,使得该引用可以在当前类中解析。将上面的类Foo改写为如下定义(译注:注意红色部分),输出将变为Bar。
Listing 8. Using the static keyword
class Foo
{
protected static $name = 'Foo';
public static function test()
{
return static::$name;
}
}
class Bar extends Foo
{
protected static $name = 'Bar';
}
echo Bar::test(); // 输出'Bar'
|
需要注意的是,static关键字的这种用法在非静态上下文中无效。也就是说普通的继承规则对静态调用无效。static关键字将简单的去尝试在当前类中进行解析,而不是函数定义所在的类。这是一个需要我们注意的事情。
在分析了一些有关静态方法和属性方面的改善之后,我们来看看PHP V5中非常重要的部分:标准PHP库中新增的一些类。
标准PHP库
PHP V5中新增的标准PHP库是用于解决标准问题的一组类和接口。这些问题包括可迭代对象、数组对象和链表等。使用这些类和接口的好处是:它们是PHP的原生方法,比用PHP实现的效率要高。 同时,它们使得许多PHP内置函数和语言结构可以直接使用这些对象,比如你可以通过foreach结构遍历一个实现了迭代器接口的对象。
PHP V5.3在SPL中增加了若干个类。如早前提到过的双向链表SplDoublyLinkedList。SPL中新增的栈和队列类:SplStack和SplQueue的实现中都使用了该类。
下面看一下如何使用SplStack类实现一个栈。
Listing 9. Using SplStack
$stack = new SplStack();
// push a few new items on the stack
$stack->push('a');
$stack->push('b');
$stack->push('c');
// see how many items are on the stack
echo count($stack); // returns 3
// iterate over the items in the stack
foreach ( $stack as $item )
echo "[$item],";
// the above outputs: [c],[b],[a]
// pop an item off the stack
echo $stack->pop(); // returns 'c'
// now see how many items are on the stack
echo count($stack); // returns 2
|
SplQueue类与SplStack相似,实现了队列功能(先进先出,而栈是先进后出)。此外,还实现了堆(SplHeap)以及一些特殊的队列和堆实现,如(SplMinHeap、SplMaxHeap和
SplPriorityQueue)。
另外新增的一个实用类是SplFixedArray,如名字暗示的那样,它是一个固定大小的数组实现。不过,它的速度比PHP内置的数组要快20-30%,原因是它是固定大小的,而内置数组是变长的,并且它只允许数字索引。下面演示了它的用法。
Listing 10. SplFixedArray
$array = new SplFixedArray(3);
$array[0] = 'dog';
$array[1] = 'cat';
$array[2] = 'bird';
$a->setSize(4); // increase the size on the fly
$array[3] = 'mouse';
foreach ( $array as $value )
echo "[$value],";
Output:
[dog],[cat],[bird],[mouse]
|
此外,还增加了两个迭代器类:FilesystemIterator和GlobIterator。它们的行为与PHP中的其它迭代器一致,只不过它们是为特定目的而定制的。
另外一个变化是SPL在PHP V5.3中将强制启用。以前的PHP V5版本,你可以在编译的时候禁止SPL,但是这个选项现在没有了。
SPL的改善提高了PHP的易用性,并实现了数据结构如双向链表、栈、堆和队列等。它们可以替换用户自己实现的这些数据结构,这样不但可以提高效率,而且还可以促进PHP函数和语言结构的整合。
讨论完SPL的改进之后,我们看看PHP V5.3如何通过采用循环垃圾回收技术来提高OOP的性能与内存消耗。
循环垃圾回收
PHP开发者经常碰到的一个性能瓶颈是垃圾回收。PHP有一个非常简单的垃圾回收器,用于回收不在作用域内的对象。它的原理是通过引用计数,当计数到达0时(意味着不再有引用指向该对象),该对象被回收,释放占用的内存。
这种方式简单高效,但是在父子关系的对象引用中会产生问题。此时,这些对象的引用计数器不为0,所以不会被回收,一直存在到请求的结束。下面我们来看这个问题的一个例子。
Listing 11. Parent-child class relationship not properly garbage collected in PHP V5.2 and earlier
class Parent
{
public function __construct()
{
$this->child = new Child($this);
}
}
class Child
{
public function __construct(
Parent $parent
)
{
$this->parent = $parent;
}
}
for ($i = 0; $i < 1000; $i++) { new Parent(); }
|
在这个例子中,你每次创建一个Parent对象,然后该对象不再被引用,但是内存不会被释放,所以脚本的内存消耗持续增加。在用户空间,存在一些方法来解决该问题,比如为Parent类提供一个析构函数,直接释放引用的子对象。但是你必须在释放Parent对象前显式调用该析构函数,如果一直这么做,将使你的代码变得非常复杂。
在PHP V5.3中,垃圾回收器将会检测这种循环引用并释放它们,从而使该脚本的内存使用保持在正常状态。当Parent对象的引用计数为0时,Parent内部引用的Child对象也将被回收。
总结
PHP在支持面向对象编程方面走过了漫漫长路,从PHP V4时的弱支持到PHP V5中的飞跃以及后续版本的改进。现在PHP V5.3又加入了一些令人兴奋的改进,包括语法上的改进,如新的__callStatic()魔术方法、动态的静态调用、静态调用的晚绑定和静态方法和属性的支持。SPL上则增加了双向链表、栈、堆和队列的实现,为开发者提供了现成的常用数据结构并提高它们的易用性。最后,期盼已久的循环垃圾回收器修复了自引用类导致的内存和性能问题,通过这个大幅改进的垃圾回收器,正确释放循环引用的对象。上述的特性,让PHP V5.3成为一个更强大的面向对象编程语言。
原文链接:http://www.ibm.com/developerworks/opensource/library/os-php-5.3new1/index.html