在面向对象的世界里,对象的另一个经典类型是接口。接口是一个描述一整套方法的对象,某类可能选择其中的方法去实现。接口如下所示。
interface interf {
public function f1($x,$y,...,);
public function f2(....);
….
public function fn(...);
}
请注意,上述代码没有方法代码的规范说明,仅有参数的名称和数量。一个类可以选择实现这样的接口,如下所示。
class c extends parent implements interf {
(all functions listed in the interface must now be defined)
…
)
接口可以从其他接口继承而来,就像类一样。语法也是相同的。
interface interf2 extends interf1 {
function f1(...);
}
新的接口interf2将包含来自接口interf1的所有函数,加上新的函数,均由interf2定义。代码清单1-9是一个例子。
代码清单1-9 新接口interf2的例子
<?php
interface i1 {
public function f1($a);
}
interface i2 extends i1 {
public function f2($a);
}
class c1 implements i2 {
private $memb;
function __construct($memb) {
$this->memb = $memb;
}
function f2($x) {
printf("Calling F2 on %s with arg: %s\n", $this->memb, $x);
}
}
$x = new c1("test");
$x->f2('a');
当执行这个脚本时,就会产生一个错误,因为接口i1的函数f1没有定义。错误的输出如下。
Fatal error: Class c1 contains 1 abstract method and must therefore be declared abstract or
implement the remaining methods (i1::f1) in /home/mgogala/work/book/script2.6.php on line 17
接口是Java编程中的标准结构,与PHP等脚本语言有稍许不同。下面将要看到的例子是关于接口Iterator的,它是PHP语言不可或缺的组成部分。迭代器是实现PHP内部接口(Iterator)的类的对象。定义接口Iterator如下所示。
interface Iterator {
public function rewind(); // 将迭代器倒回到第一个元素
public function next(); // 向前移到下一个元素
public function key(); // 返回当前元素的键
public function current(); // 返回当前的元素
public function valid(); // 检查当前位置是否有效
}
实现接口Iterator的任何类都可以在for循环中使用,它们的对象被称为迭代器(iterator)。如代码清单1-10所示。
代码清单1-10 实现接口Iterator的任何类都可以在for循环中使用
<?php
class iter implements iterator {
private $items;
private $index = 0;
function __construct(array $items) {
$this->items = $items;
}
function rewind() {
$this->index = 0;
}
function current() {
return ($this->items[$this->index]);
}
function key() {
return ($this->index);
}
function next() {
$this->index++;
if (isset($this->items[$this->index])) {
return ($this->items[$this->index]);
} else {
return (NULL);
}
}
function valid() {
return (isset($this->items[$this->index]));
}
}
$x = new iter(range('A', 'D'));
foreach ($x as $key => $val) {
print "key=$key\tvalue=$val\n";
}
这个关于PHP迭代器的例子,尽管非常典型,但其实很简单。执行时,这个脚本就会生成下面的输出。
key=0 value=A
key=1 value=B
key=2 value=C
key=3 value=D
脚本的主要组成部分,即整个练习的原因所在,是位于脚本最下面的循环。此语法也经常为数组使用,但$x不是一个数组,它是类iter的一个对象。迭代器是可以像数组一样表现的对象。这是通过实施接口Iterator实现的。这在什么情况下是适用的呢?文件里的行或由游标返回的行可以很轻松地迭代通过。请注意,我们仍然不能使用表达式$x[$index];计数变量只是用于顺序遍历数组。通过实现Iterator接口完成的话,将会是一个相当繁琐的练习,就如代码清单1-11。
代码清单1-11 实现接口Iterator
<?php
class file_iter implements iterator {
private $fp;
private $index = 0;
private $line;
function __construct($name) {
$fp = fopen($name, "r");
if (!$fp) {
die("Cannot open $name for reading.\n");
}
$this->fp = $fp;
$this->line = rtrim(fgets($this->fp), "\n");
}
function rewind() {
$this->index = 0;
rewind($this->fp);
$this->line = rtrim(fgets($this->fp), "\n");
}
function current() {
return ($this->line);
}
function key() {
return ($this->index);
}
function next() {
$this->index++;
$this->line = rtrim(fgets($this->fp), "\n");
if (!feof($this->fp)) {
return ($this->line);
} else {
return (NULL);
}
}
function valid() {
return (feof($this->fp) ? FALSE : TRUE);
}
}
$x = new file_iter("qbf.txt");
foreach ($x as $lineno => $val) {
print "$lineno:\t$val\n";
}
qbf.txt文件是一个小的文本文件,包含著名的全字母短句(pangram),即该谚语包含所有26个英文字母。
quick brown fox
jumps over
the lazy dog
这个脚本会读取文件,并在屏幕上打印出来,每行前面是行号。它使用标准文件进行操作,如fopen、fgets和rewind。rewind函数不仅仅是迭代器接口里的一个方法名称,它还是一个操纵文件的核心函数,修改文件处理,指向文件的开头。
行号从0开始,目的在于使文件尽可能类似于数组。到目前为止,我们已经见过成为迭代器的文件和数组。具有“get next”和“am I done”方法的任何类型的实体可以用一个迭代结构代表,并循环遍历。数据库游标就是这样一个例子。它有“get next”方法,称为“fetch”;同时,利用handle status,它还能够告知何时取回最后一条记录。对数据库游标实现迭代器类的操作与代码清单1-11中对文件所做的操作非常类似。file_iter类只是一个例子。PHP 5包含了一套命名为标准PHP库(SPL)的内部类,类似于C++著名的STL。SPL的SplFileObject类更为详尽,并且它的确实现了迭代器类。我们的整个脚本可以写得更简单,就像下面这样。
<?php
$x = new SplFileObject("qbf.txt","r");
foreach ($x as $lineno => $val) {
if (!empty($val)) {print "$lineno:\t$val"; }
}
?>
请注意,新行字符不再从该行剥离。我们必须测试该行为空行。如果我们忽视测试空行,SplFileObject类将推进文件结束。然而,它仍然是一个使工作变得更轻松的类。唯一一个真正有用的函数是SplFileClass缺失的fputcsv,该函数输出CSV格式的数组,而且很容易编写。
在SPL还有其他有用的类和接口。有关SPL的内容超出了本书的范围,有兴趣的读者可以在以下网站查找相关信息:www.php.net/manual/en/book.spl.php。
也有一套实现了数据库的游标和查询的迭代器的类的标准。这套类被称为ADOdb,它使程序员就像通过一个文件或一个数组,使用foreach循环遍历查询结果。ADOdb类集将在后续章节更详细地介绍。
抽象类和接口之间的区别是什么?两者均被用作模板,其他类从而可以继承或实现它们。但抽象类更严格,结构定义也更为严格。除了抽象方法,抽象类可以有真正的成员和方法,甚至是final方法,不能重载,只能那么用。