高级PHP V5 对象研究
本文介绍了PHP V5一些更高级的面向设计的特性。其中包括各种对象类型,它们允许将系统中的组件相互分离,创建可重用、可扩展、可伸缩的代码。
领会暗示
首先介绍一下对象类型和类型提示的优点。一个类定义一种类型。从该类实例化的任何对象属于该类定义的类型。所以,使用 Car 类创建 Car 对象。如果 Car 类继承 Vehicle 超类,则 Car 对象还将是一个 Vehicle 对象。这反映了我们在现实世界中分类事物的方法。但正如您将看到的,类型不仅仅是分类系统元素的有用方法。类型是面向对象编程的基础,因为类型是良好一致的行为的保证。许多设计技巧来自该保证。
“开始了解 PHP V5 中的对象”展示对象为您保证了接口。当系统传递 Dictionary 对象时,您可以确定它具有 $translations 数组和 summarize() 方法。相反,关联数组不提供相同级别的确定性。要利用类提供的清晰接口,需要知道您的对象实际上是 Dictionary 的一个实例,而不是某个 imposter。可以用 instanceof 操作符来手动验证这一点,该操作符是 PHP V5 引入的介于对象实例和类名之间的一个便捷工具。
instanceof Dictionary
如果给定对象是给定类的实例,则 instanceof 操作符解析为真。在调用方法中第一次遇到 Dictionary 对象时,可以在使用它之前检查它的类型。
if ( $en instanceof Dictionary ) {
print $en->summarize();
}
但是,如果使用 PHP V5 的话,可以将对象类型检查构建到类或方法声明中。
在“开始了解 PHP V5 中的对象”中,重点介绍两个类:Dictionary,它存储术语和翻译, DictionaryIO,它将 Dictionary 数据导出(导入)自(至)文件系统。这些特性使得将 Dictionary 文件发送到第三方翻译器变得容易,第三方翻译器可以使用自己的软件来编辑数据。然后,您可以重新导入已处理的文件。清单 1 是 Dictionary 类的一个版本,它接受一个 DictionaryIO 对象,并将其存储以备将来使用。
清单 1. 接受 DictionaryIO 对象的 Dictionary 类的一个版本
class Dictionary {
public $translations = array();
public $type ="En";
public $dictio;
function addDictionaryIO( $dictio ) {
$this->dictio=$dictio;
}
function export() {
if ( $this->dictio ) {
$this->dictio->export( $this );
}
}
}
class DictionaryIO {
function export( $dict ) {
print "exporting dictionary data "."($dict->type)
";
}
}
$en = new Dictionary();
$en->addDictionaryIO( new DictionaryIO() );
$en->export();
// output:
// dumping dictionary data (En)
DictionaryIO 类具有单个方法 export(),它接受一个 Dictionary 对象,并使用它来输出假消息。现在,Dictionary 具有两个新方法:addDictionaryIO(),接受并存储 DictionaryIO 对象; export(),使用已提供的对象导出 Dictionary 数据 —— 或者是在完全实现的版本中。
您可能会疑惑为什么 Dictionary 对象不仅实例化自己的 DictionaryIO 对象,或者甚至在内部处理导入导出操作,而根本不求助于第二个对象。一个原因是您可能希望一个 DictionaryIO 对象使用多个 Dictionary 对象,或者希望存储该对象的单独引用。另一个原因是通过将 DictionaryIO 对象传递给 Dictionary,可以利用类切换或 多态性。换句话说,可以将 DictionaryIO 子类(比如 XmlDictionaryIO)的实例传递给 Dictionary,并更改运行时保存和检索数据的方法。
图 1 显示了 Dictionary 和 DictionaryIO 类及其使用关系。
As shown, there is nothing stopping the encoder from passing a completely random object to addDictionaryIO(). Only when running export() do I get a similar error and discover that the object already stored in $dictio doesn't actually have an export() method. When using PHP V4, the parameter types in this example must be tested to be absolutely sure that the encoder is passing an object of the correct type. When using PHP V5, parameter hints can be deployed to enforce object types. Add only the required object types to the parameter variables of the method declaration, as shown in Listing 2:
Listing 2. Add the object type to the parameter variable of the method declaration
function addDictionaryIO( DictionaryIO $dictio ) {
$this->dictio=$dictio;
}
function export() {
if ( $this->dictio ) {
$this->dictio->export( $this );
}
}
The PHP engine will now throw a fatal error if the client coder attempts to pass an object of the wrong type to addDictionaryIO(). Therefore, type hints make code safer. Unfortunately, hints only work on objects, so you can't ask for strings or integers in the argument list. These primitive types must be tested manually.
Even though it is guaranteed that addDictionaryIO() will get the correct object type, there is no guarantee that the method is called first. The export() method tests the presence of the $dictio attribute in the export() method, thereby avoiding errors. But you might want to be stricter and require a DictionaryIO object to be passed to the constructor, thus ensuring that $dictio is always filled.
Call the override method
In Listing 3, XmlDictionaryIO integrates DictionaryIO. While DictionaryIO writes and reads serialized data, XmlDictionaryIO operates on XML and can be shared with third-party applications. XmlDictionaryIO can override its parent methods (import() and export()) or choose not to provide its own implementation (path()). If the client calls the path() method in the XmlDictionaryIO object, the path() method implemented in DictionaryIO is called.
In fact, both methods can be used at the same time. Methods can be overridden and the parent implementation called. For this purpose, use the new keyword parent. Use parent with the scope resolution operator and the name of the method in question. For example, assume that XmlDictionaryIO is required to use a directory called xml in the current working directory (if one is available); otherwise, it should use the default path generated by the parent DictionaryIO class, as shown in Listing 3:
Listing 3. XmlDictionaryIO using the xml directory or the default path generated by the DictionaryIO class
class XmlDictionaryIO extends DictionaryIO {
Function path( Dictionary $dictionary, $ext ) {
$sep = DIRECTORY_SEPARATOR;
if ( is_dir( ".{$sep}xml" ) ) {
return ".{$sep} xml{$sep}{$dictionary->getType()}.$ext";
}
return parent::path( $dictionary, $ext );
}
// . ..
As you can see, this method checks the local xml directory. If the test fails, it is assigned to the parent method using the parent keyword.
Subclasses and constructor methods
The parent keyword is especially important in constructor methods. If you don't define a constructor in a child class, the parent constructor is called explicitly on your behalf. If constructor method is not created in subclass. It is your responsibility to call the parent class's constructor and pass any parameters, as shown in Listing 4:
Listing 4. Invoking the parent class's constructor
class SpecialDictionary extends Dictionary { function __construct( $type, DictionaryIO $dictio, $additional ) {
// do something with $additional
parent::__construct( $type, $dictio );
}
}
Abstract Classes and Methods
While it is perfectly legal to provide default behavior in a parent class, it may not be the neatest approach. For starters, you must rely on the author of the subclass to understand that they must implement import() and export() in order to create the class in the broken state. Also, the DictionaryIO class is actually a brother, not a parent and son. XmlDictionaryIO is not a special case of DictionaryIO; instead, it is an alternative implementation.
PHP V5 allows the definition of partially implemented classes, whose main role is to specify the core interface for its children. Such classes must be declared abstract.
abstract class DictionaryIO {}
Abstract classes cannot be instantiated. You must subclass (that is, create a class that inherits from it), and create an instance of that subclass. Standard and abstract methods can be declared in abstract classes, as shown in Listing 5. Abstract method