[摘要]本文描述 PHP V5 中对象和类的基础知识,从最基本的概念一直讲到继承,主要针对经验丰富的面向对象程序员和尚未接触过对象的读者。作为 PHP 程序员,您肯定知道变量和函数。但类和对象可能就是另一回...
本文描述 PHP V5 中对象和类的基础知识,从最基本的概念一直讲到继承,主要针对经验丰富的面向对象程序员和尚未接触过对象的读者。
作为 PHP 程序员,您肯定知道变量和函数。但类和对象可能就是另一回事。不定义单个类,就可以创建完美的系统。但即使您决定在自己的代码中不使用面向对象的编程,您仍可能需要了解面向对象的编程。例如,如果使用第三方库,比如通过 PHP Extension and Application Repository (PEAR) 可以使用的库,您将发现自己在实例化对象和调用方法。
什么是类和对象?
简单地说,类 是一个由变量和方法组成的独立块或束。这些组件通常结合实现单个责任或一组责任。在本文中,您将创建一个类,该类收集了用于查询和填充由项和值组成的词典的方法。
类可以直接用作组织数据和功能的简单方法,就像一组函数和变量一样。但使用类可以忽略它的存在。类可用于在内存中生成多个实例。这样的实例叫做对象。每个对象可以访问一组相同的函数(在面向对象上下文中叫做方法)和变量(叫做特性或实例变量),但每个变量的实际值在每个对象中是不同的。
考虑角色扮演游戏中的一个单元——比如坦克。类可能为坦克设置一组变量:防御和进攻能力,范围,健康状况,等等。该类也可能定义一组函数,其中包括 move()
和 attack()
。当系统包含一个坦克类时,该类可用于生成数十个或数百个坦克对象,每个对象都潜在地具有自己的健康状况或范围特征。因此,类是用于生成对象的蓝图或模板。
理解类和对象最简单的方法可能就是创建一些类和对象。
第一个类
可以用 class
关键字创建类。最简单的情况是,类由关键字类、名称和代码块组成:
类名可以包含字母、数字和下划线字符的任何组合,但不能以数字打头。
上例中的 Dictionary
类尽管用处有限,但完全合法。那么如何使用该类来创建一些对象呢?
$obj1 = new Dictionary();$obj2 = new Dictionary();$obj3 = new Dictionary();
|
至少在形式上,实例化对象与调用函数相似。对于函数调用,必须提供圆括号。与函数一样,一些类需要您为其传递参数。您还必须使用 new 关键字。这就告诉 PHP 引擎您希望实例化一个新对象。然后,返回的对象可以存储在一个变量中以供将来使用。
属性
在类的主体中,可以声明叫做属性的特殊变量。在 PHP V4 中,属性必须用关键字 var
调用。这仍是合法的语法,但主要是为了向后兼容。在 PHP V5 中,属性必须声明为 public、private 或 protected。可以在 关键字:在此我们是否可以有一点隐私?中阅读有关这些限定词的内容。但现在在例子中将所有属性声明为 public。清单 1 显示一个声明了两个属性的类。
清单 1. 声明两个属性的类class Dictionary { public $translations = array(); public $type ="En";}
|
正如所看到的,可以同时声明属性并为其赋值。可以用 print_r()
函数快速浏览一下对象的状态。清单 2 显示 Dictionary
对象现在具有更多成员。
清单 2. Dictionary
对象一览$en = new Dictionary();print_r( $en ); 如果运行该脚本,将看到如下对象的输出:Dictionary Object( [translations] => Array ( ) [type] => En)
|
可以使用对象操作符 ->
访问公共对象属性。所以 $en->type
表示由 $en 引用的 Dictionary
对象的 $type 属性。如果可以访问属性,就意味着可以设置和获得其值。清单 3 中的代码创建 Dictionary
类的两个实例 —— 换言之,它实例化两个 Dictionary
对象。它更改一个对象的 $type
属性,并添加两个对象的翻译:
清单 3. 创建 Dictionary
类的两个实例$en = new Dictionary();$en->translations['TREE'] = "tree";$fr = new Dictionary();$fr->type = "Fr";$fr->translations['TREE'] = "arbre";foreach ( array( $en, $fr ) as $dict ) { print "type: {$dict->type} "; print "TREE: {$dict->translations['TREE']}\n";}
|
该脚本输出如下
type: En TREE: treetype: Fr TREE: arbre
|
所以 Dictionary
类现在比较有用了。单个对象可以存储不同的键值组合,还有一个标志,该标志告诉客户端有关这种 Dictionary
的详细信息。
尽管 Dictionary
类当前与关联数组的包装器相差无几,但这里有一些了解对象功能的线索。目前,我们已经可以很好地表示我们的示例数据了,如清单 4 所示。
清单 4. 示例数据$en = array( 'translations'=>array( 'TREE' => 'tree' ), 'type'=>'En');$fr = array( 'translations'=>array( 'TREE' => 'arbre' ), 'type'=>'Fr');
|
虽然该数据结构完成了与 Dictionary
类相同的目的,但它没有提供结构的保证。如果传递 Dictionary
对象,我们知道它具有 $translations
属性。但如果是一个关联数据,则没有这样的保证。这个事实使得类似 $fr['translations']['TREE'];
的查询有些碰运气,除非进行查询的代码确定数组的起源。这是对象的重点:对象的类型是其特征的保证。
虽然用对象存储数据有优点,但是您可能没有一点感觉。对象可以是东西,但关键在于它们还可以做事情。
方法
简单地说,方法是在类中声明的函数。它们通常(但不总是)通过对象实例使用对象操作符来调用的。清单 5 向 Dictionary
类中添加一个方法,并调用该方法。
清单 5. 向 Dictionary
类中添加方法class Dictionary { public $translations = array(); public $type ="En"; function summarize() { $ret = "Dictionary type: {$this->type}\n"; $ret .= "Terms: ".count( $this->translations )."\n"; return $ret; }}$en = new Dictionary();$en->translations['TREE'] = "tree";print $en->summarize();
|
它提供如下输出:
Dictionary type: EnTerms: 1
|
正如所看到的,声明 summarize()
方法与声明任何函数的方式一样,只不过它是在类中声明。summarize()
方法是通过 Dictionary
实例使用对象操作符调用的。summarize()
函数访问属性来提供对象状态的简述。
注意对于本文来说的一个新特性的用法。$this
伪变量提供了一种用于对象引用自己的属性和方法的机制。在对象外部,可以使用句柄来访问它的元素(在本例子中是 $en
)。在对象内部,则无此句柄,所以必须求助于 $this
。如果觉得 $this
有些迷惑,则在代码中遇到它时,试着在头脑中用当前实例 替换它。
类通常使用通用建模语言 (Universal Modeling Language,UML) 表示在图表中。UML 的详细信息超出了本文的范围,但这种图表不过是一种可视化类关系的好方法。图 1 显示用 UML 表示的 Dictionary
类。类名位于顶层,属性在中间,方法在底层。
图 1. 使用 UML 显示 Dictionary 类

构造函数
PHP 引擎识别许多“魔术”方法。如果定义了方法,则 PHP 引擎将在相应的情况发生时自动调用这些方法。最常实现的方法是构造函数方法。PHP 引擎在实例化对象时调用构造函数。对象的所有基本设置代码都放在构造函数中。在 PHP V4 中,通过声明与类同名的方法来创建构造函数。在 V5 中,应声明叫做 __construct()
的方法。清单 6 显示需要 DictionaryIO
对象的构造函数。
清单 6. 需要 DictionaryIO
对象的构造函数class Dictionary { public $translations = array(); public $type; public $dictio; function __construct( $type, DictionaryIO $dictio ) { $this->type = $type; $this->dictio=$dictio; } //...
|
要实例化 Dictionary
对象,需要将类型字符串和 DictionaryIO
对象传递给它的构造函数。构造函数使用这些参数来设置自有属性。下列代码显示可以如何实例化 Dictionary
对象:
$en = new Dictionary( "En", new DictionaryIO() );
|
Dictionary
类现在比以前更安全。所有 Dictionary
对象都已经用必需的参数初始化过了。
当然,还无法阻止一些人随后更改 $type
属性或将 $dictio
设置为空。可喜的是,PHP V5 可以帮助您实现这一功能。
关键字:在此我们是否可以有一点隐私?
前面已经看到与属性声明相关的 public 关键字。该关键字表示属性的可见度。事实上,属性的可见度可以设置为 public、private 和 protected。声明为 public 的属性可以在类外部写入和读取,声明为 private 的属性只在对象或类上下文中可见。声明为 protected 的属性只能在当前类及其子类的上下文中可见。(在 继承 部分将会看到这些内容起作用。)可以使用 private 属性来真正锁定类。如果将属性声明为 private 并试图从类范围外部访问它(如清单 7 所示),PHP 引擎将抛出致命错误。
清单 7. 试图从类范围外部访问属性class Dictionary { private $translations = array(); private $dictio; private $type; function __construct( $type, DictionaryIO $dictio ) { $this->type = $type; $this->dictio = $dictio; } // ...}$en = new Dictionary( "En", new DictionaryIO() );$en->dictio = null;
|
输出如下:
Fatal error: Cannot access private property Dictionary::$dictio in...
|
一般来说,应将大多数属性声明为 private,然后根据需要提供获得和设置这些属性的方法。这样就可以控制类的接口,使一些数据只读,在将参数分配给属性之前对参数进行清理或过滤,并提供与对象交互的一套明确的规则。
修改方法可见度的方法与修改属性可见度的方法一样,即在方法声明中添加 public、private 或 protected。如果类需要使用一些外部世界无需知道的家务管理方法,则可以将其声明为 private。在清单 8 中,get()
方法为 Dictionary
类的用户提供了提取翻译的接口。该类还需要跟踪所有查询,因此提供了 private 方法 logQuery()
。
清单 8. get()
方法为 Dictionary
类的用户提供了接口function get( $term ) { $value = $this->translations[$term]; $this->logQuery( $term, $value, "get" ); return $value;}private function logQuery( $term, $value, $kind ) { // write log information}
|
将 logQuery()
声明为 private 简化了公共接口,而且防止了类不适当地调用 logQuery()
。与属性一样,尝试从包含类外部调用私有方法将导致致命错误。
在类上下文操作
到目前为止,所看到的方法和属性都在对象上下文中进行操作。也就是说,必须使用对象实例,通过 $this
伪变量或标准变量中存储的对象引用来访问方法和属性。有时候,可能发现通过类而不是对象实例来访问属性和方法更有用。这种类成员叫做静态 成员。
要声明静态属性,将关键字 static
放在可见度修饰符后面,直接位于属性变量前面。
下例显示单个静态属性:$iodir
,存放用于保存和读取 Dictionary
数据的默认目录的路径。因为该数据对于所有对象是相同的,所以让它可用于所有实例是有意义的。
清单 9. 单个静态 $iodir
属性class Dictionary { public static $iodir="."; // ...}
|
可以使用范围解析操作符来访问静态属性,该操作符由双冒号 (::) 组成。范围解析操作符应位于类名和希望访问的静态属性之间。
print Dictionary::$iodir . "\n";Dictionary::$iodir = "/tmp";
|
正如所看到的,访问该属性无需实例化 Dictionary
对象。
声明和访问静态方法的语法与此相似。再次,应将 static 关键字放在可见度修饰符后。清单 10 显示了两个静态方法,它们访问声明为 private 的 $iodir
属性。
清单 10. 访问 $iodir
属性的两个静态方法class Dictionary { private static $iodir="."; // ... public static function setSaveDirectory( $dir ) { if ( ! is_dir( $dir )
|
关键词:开始知道 PHP V5 中的对象