[摘要]! is_writable( dir ) ) return false; self::iodir = dir; public st...
! is_writable( $dir ) ) { return false; } self::$iodir = $dir; } public static function getSaveDirectory( ) { return self::$iodir; } // ...}
用户不再能访问 $iodir
属性目录了。通过创建特殊方法来访问属性,可以确保所提供的任何值是健全的。在本例中,方法在进行分配前检查给定字符串指向可写入的目录。
注意,两个方法都使用关键字 self
和访问解析操作符来引用 $iodir
属性。不能在静态方法中使用 $this
,因为 $this
是对当前对象实例的引用,但静态方法是通过类而不是通过对象调用的。如果 PHP 引擎在静态方法中看到 $this
,它将抛出致命错误和一条提示消息。
要从类外部调用静态方法,可使用类名加上范围解析符和方法名。
Dictionary::setSaveDirectory("/tmp");print Dictionary::getSaveDirectory();
|
需要使用静态方法有两个重要原因。首先,实用程序操作可能不需要对象实例来做它的工作。通过声明为静态,为客户机代码节省了创建对象的工作量。第二,静态方法是全局可用的。这意味着可以设置一个所有对象实例都可以访问的值,而且使得静态方法成为共享系统上关键数据的好办法。
尽管静态属性通常被声明为 private 来防止别人干预,但有一种方法可以创建只读静态范围的属性,即声明常量。与全局属性一样,类常量一旦定义就不可更改。它用于状态标志和进程生命周期中不发生更改的其他东西,比如 pi 或非洲的所有国家。
用 const
关键字声明类常量。例如,因为 Dictionary
对象的实际实现背后几乎肯定有一个数据库,所以还可以假设项和翻译有最大长度。清单 11 将其设置为类常量。
清单 11. 将 MAXLENGTH
设置为类常量class Dictionary { const MAXLENGTH = 250; // ... }print Dictionary::MAXLENGTH;
|
类常量始终为 public,所以不能使用可见度关键字。这并是问题,因为任何更改其值的尝试都将导致解析错误。还要注意,与常规属性不同,类常量不以美元符号开始。
继承
如果熟悉面向对象编程,您将知道我一直把最好的留到最后。类及其生成的动态对象之间的关系使得系统更灵活。例如,每个 Dictionary
对象封装不同的翻译数据集合,但是这些不同实体的模型定义在单个 Dictionary
类中。
但有时候需要记下类级别的差异。是否记得 DictionaryIO
类?扼要重述一下,它从 Dictionary
对象中获取数据,将其写入文件系统,从一个文件中获取数据,将其合并回到 Dictionary
对象中。清单 12 显示使用序列化来保存和加载 Dictionary
数据的快速实现。
清单 12. 使用序列化的快速实现class Dictionary { // ... function asArray() { return $this->translations; } function getType() { return $this->type; } function export() { $this->dictio->export( $this ); } function import() { $this->dictio->import( $this ); }}class DictionaryIO { function path( Dictionary $dictionary, $ext ) { $path = Dictionary::getSaveDirectory(); $path .= DIRECTORY_SEPARATOR; $path .= $dictionary->getType().".$ext"; return $path; } function export( Dictionary $dictionary ) { $translations = $dictionary->asArray(); file_put_contents( $this->path( $dictionary, 'serial'), serialize( $translations ) ); } function import( Dictionary $dictionary ) { $path = $this->path( $dictionary, 'serial' ); if ( ! is_file( $path ) ) return false; $translations = unserialize( file_get_contents( $path ) ); foreach ( $translations as $term => $trans ) { $dictionary->set( $term, $trans ); } }}$dict = new Dictionary( "En", new DictionaryIO() );$dict->set( "TREE", "tree" );$dict->export();
|
本例引入两个简单的 Dictionary
方法,具体来说,asArray()
返回 $translations
数组的副本。DictionaryIO
实现具有简约的优点。因为在示例代码中通常省略了错误检查,即便如此,这仍是将数据保存到文件中的快速简单的方法。
一旦部署了这种库之后,则需要立即支持它的保存格式。让格式过时会冒犯那些可能以这种方式存储备份的用户的愿望。但要求改变了,而且还可能收到输出格式不方便用户编辑的抱怨。这些用户希望将导出文件以 XML 格式发送给第三方。
现在面临一个问题。如何在 DictionaryIO
接口中支持两种格式?
一个解决方案是在 export()
和 import()
方法中使用条件语句,测试类型标志,如清单 13 所示。
清单 13. 在 export()
和 import()
方法中使用条件语句function export( Dictionary $dictionary ) { if ( $this->type == DictionaryIO::SERIAL ) { // write serialized data } else if ( $this->type == DictionaryIO::XML ) { // write xml data }}function import( Dictionary $dictionary ) { if ( $this->type == DictionaryIO::SERIAL ) { // read serialized data } else if ( $this->type == DictionaryIO::XML ) { // read xml data }}
|
这种结构是坏“代码味道”的一个例子,原因在于它依赖于复制。在一个地方进行更改(比如,添加新类型测试)需要在其他地方进行一组相应的更改(将其他类型测试带入行中),代码很快就会变得易错难读。
继承提供了更优雅的解决方案。可以创建一个新类 XmlDictionaryIO
,该类继承由 DictionaryIO
设置的接口,但覆盖其中一些功能。
使用 extends 关键字创建子类。如下是 XmlDictionaryIO
类的最小实现:
XmlDictionaryIO extends DictionaryIO {}
|
XmlDictionaryIO
现在的功能与 DictionaryIO
完全相同。因为它从 DictionaryIO
继承了所有的公共(和保护)属性,所以可以将应用于 DictionaryIO
对象的相同操作应用于 XmlDictionaryIO
对象。这种关系扩展到对象类型。XmlDictionaryIO
对象显然是 XmlDictionaryIO
类的实例,但它也是 DictionaryIO
的实例 —— 同样地,以一般化的顺序,一个人同时是人类、哺乳动物和动物。可以使用 instanceof
操作符来测试这一点,如果对象是指定类的成员,则返回 true,如清单 14 所示。
清单 14. 使用 instanceof
操作符测试继承$dictio = new XmlDictionaryIO();if ( $dictio instanceof XmlDictionaryIO ) { print "object is an instance of XmlDictionaryIO\n";}if ( $dictio instanceof DictionaryIO ) { print "object is an instance of DictionaryIO\n";}
|
输出如下:
object is an instance of XmlDictionaryIOobject is an instance of DictionaryIO
|
正如 instanceof
接受 $dictio
是 DictionaryIO
对象,所以方法也将接受这些对象作为参数。这意味着 XmlDictionaryIO
对象可以被传递给 Dictionary
类的构造函数,即使 DictionaryIO
是由构造函数的签名指定的类型。
清单 15 是快而脏的 XmlDictionaryIO
实现,使用 DOM 来完成 XML 功能。
清单 15. XmlDictionaryIO
实现class XmlDictionaryIO extends DictionaryIO { function export( Dictionary $dictionary ) { $translations = $dictionary->asArray(); $doc = new DOMDocument("1.0"); $dic_el = $doc->createElement( "dictionary" ); $doc->appendChild( $dic_el ); foreach ( $translations as $key => $val ) { $term_el = $doc->createElement( "term" ); $dic_el->appendChild( $term_el ); $key_el = $doc->createElement("key", $key ); $val_el = $doc->createElement( "value", $val ); $term_el->appendChild( $key_el ); $term_el->appendChild( $val_el ); } file_put_contents( $this->path( $dictionary, 'xml'), $doc->saveXML() ); } function import( Dictionary $dictionary ) { $path = $this->path( $dictionary, 'xml'); if ( ! is_file( $path ) ) return false; $doc = DOMDocument::loadXML( file_get_contents( $path ) ); $termlist = $doc ->getElementsByTagName( "term" ); foreach ( $termlist as $term ) { $key = $term->getElementsByTagName( "key" ) ->item( 0 )->nodeValue; $val = $term ->getElementsByTagName( "value" ) ->item( 0 )->nodeValue; $dictionary->set( $key, $val ); } }}
|
有关获得并生成 XML 的详细信息是当然要介绍的。有许多方法能完成这一操作,其中包括完美的 SimpleXML 扩展。简言之,import()
方法以 XML 文档为参数,并使用它来填充 Dictionary
对象。export()
方法从 Dictionary
对象中取得数据,并将其写入 XML 文件中。(在现实世界中,可能会使用叫做 XLIFF 的基于 XML 的格式,该格式适用于导入到第三方翻译工具中。)
注意,import()
和 export()
都调用实用程序方法 path()
,该方法不存在于 XmlDictionaryIO
类中。但没有关系,因为 path()
在 DictionaryIO
中实现。当 XmlDictionaryIO
实现一个方法时,则当调用该方法时,会为 XmlDictionaryIO
对象调用该实现。当没有任何实现存在时,调用失败返回给父类。
图 2 显示了 DictionaryIO
和 XmlDictionaryIO
类之间的继承关系。封闭的箭头表示继承,从子类指向父类。
图 2. 继承关系

结束语
由于篇幅有限,因此不可能全部介绍。进一步研究有两个方向:广度和深度。广度指的是超出本文范围的那些特性,比如抽象类、接口、迭代器接口、反射、异常和对象复制。深度指的是设计问题。尽管理解 PHP 中可用于面向对象编程的工具范围很重要,但考虑如何最佳使用这些特性同样重要。幸运的是,专门讲述面向对象上下文中设计模式的可用参考资料很多(参阅“参考资料”)
如果仍在使用 PHP V4 编程,我希望您查找足够新的特性来证明迁移到 V5 及其面向对象核心特性是正确的。不久您就会惊讶地发现自己无法离开它们了。
关键词:开始知道 PHP V5 中的对象