当前位置: 首页 > 图文教程 > 网络编程 > PHP > 高级PHP V5 对象研究
高级PHPV5对象研究
本文介绍了PHPV5一些更高级的面向设计的特性。其中包括各种对象类型,它们允许将系统中的组件相互分离,创建可重用、可扩展、可伸缩的代码。
领会暗示
首先介绍一下对象类型和类型提示的优点。一个类定义一种类型。从该类实例化的任何对象属于该类定义的类型。所以,使用Car类创建Car对象。如果Car类继承Vehicle超类,则Car对象还将是一个Vehicle对象。这反映了我们在现实世界中分类事物的方法。但正如您将看到的,类型不仅仅是分类系统元素的有用方法。类型是面向对象编程的基础,因为类型是良好一致的行为的保证。许多设计技巧来自该保证。
“开始了解PHPV5中的对象”展示对象为您保证了接口。当系统传递Dictionary对象时,您可以确定它具有$translations数组和summarize()方法。相反,关联数组不提供相同级别的确定性。要利用类提供的清晰接口,需要知道您的对象实际上是Dictionary的一个实例,而不是某个imposter。可以用instanceof操作符来手动验证这一点,该操作符是PHPV5引入的介于对象实例和类名之间的一个便捷工具。
instanceofDictionary
如果给定对象是给定类的实例,则instanceof操作符解析为真。在调用方法中第一次遇到Dictionary对象时,可以在使用它之前检查它的类型。
if($eninstanceofDictionary){
print$en->summarize();
}
但是,如果使用PHPV5的话,可以将对象类型检查构建到类或方法声明中。
在“开始了解PHPV5中的对象”中,重点介绍两个类:Dictionary,它存储术语和翻译,DictionaryIO,它将Dictionary数据导出(导入)自(至)文件系统。这些特性使得将Dictionary文件发送到第三方翻译器变得容易,第三方翻译器可以使用自己的软件来编辑数据。然后,您可以重新导入已处理的文件。清单1是Dictionary类的一个版本,它接受一个DictionaryIO对象,并将其存储以备将来使用。
清单1.接受DictionaryIO对象的Dictionary类的一个版本
classDictionary{
public$translations=array();
public$type="En";
public$dictio;
functionaddDictionaryIO($dictio){
$this->dictio=$dictio;
}
functionexport(){
if($this->dictio){
$this->dictio->export($this);
}
}
}
classDictionaryIO{
functionexport($dict){
print"exportingdictionarydata"."($dict->type)\n";
}
}
$en=newDictionary();
$en->addDictionaryIO(newDictionaryIO());
$en->export();
//output:
//dumpingdictionarydata(En)
DictionaryIO类具有单个方法export(),它接受一个Dictionary对象,并使用它来输出假消息。现在,Dictionary具有两个新方法:addDictionaryIO(),接受并存储DictionaryIO对象;export(),使用已提供的对象导出Dictionary数据——或者是在完全实现的版本中。
您可能会疑惑为什么Dictionary对象不仅实例化自己的DictionaryIO对象,或者甚至在内部处理导入导出操作,而根本不求助于第二个对象。一个原因是您可能希望一个DictionaryIO对象使用多个Dictionary对象,或者希望存储该对象的单独引用。另一个原因是通过将DictionaryIO对象传递给Dictionary,可以利用类切换或多态性。换句话说,可以将DictionaryIO子类(比如XmlDictionaryIO)的实例传递给Dictionary,并更改运行时保存和检索数据的方法。
图1显示了Dictionary和DictionaryIO类及其使用关系。
正如所显示的,没有什么阻止编码器将完全随机的对象传递给addDictionaryIO()。只有在运行export()时,才会获得一个类似的错误,并发现已经存储在$dictio中的对象实际上并没有export()方法。使用PHPV4时,必须测试本例中的参数类型,以绝对确保编码器传递类型正确的对象。使用PHPV5时,可以部署参数提示来强制对象类型。只将所需的对象类型添加到方法声明的参数变量中,如清单2所示:
清单2.将对象类型添加到方法声明的参数变量中
functionaddDictionaryIO(DictionaryIO$dictio){
$this->dictio=$dictio;
}
functionexport(){
if($this->dictio){
$this->dictio->export($this);
}
}
现在,如果客户机编码器试图将类型错误的对象传递给addDictionaryIO(),PHP引擎将抛出一个致命错误。因此,类型提示使得代码更安全。不幸的是,提示仅对对象有效,所以不能在参数列表中要求字符串或整数。必须手动测试这些原类型。
即使可以保证addDictionaryIO()将获得正确的对象类型,但不能保证该方法被首先调用。export()方法测试export()方法中$dictio属性的存在,从而避免错误。但您可能希望更严格一些,要求DictionaryIO对象传递给构造函数,从而确保$dictio总是被填充。
调用覆盖方法
在清单3中,XmlDictionaryIO集成DictionaryIO。而DictionaryIO写入并读取序列化数据,XmlDictionaryIO操作XML,可以与第三方应用程序共享。XmlDictionaryIO可以覆盖其父方法(import()和export()),也可以选择不提供自己的实现(path())。如果客户机调用XmlDictionaryIO对象中的path()方法,则在DictionaryIO中实现的path()方法被调用。
事实上,可以同时使用这两种方法。可以覆盖方法并调用父实现。为此,使用新关键字parent。用范围解析操作符和所讨论方法的名称来使用parent。例如,假设需要XmlDictionaryIO使用当前工作目录(如果有一个可用)中叫做xml的目录;否则,它应使用由父DictionaryIO类生成的默认路径,如清单3所示:
清单3.XmlDictionaryIO使用xml目录或由DictionaryIO类生成的默认路径
classXmlDictionaryIOextendsDictionaryIO{
functionpath(Dictionary$dictionary,$ext){
$sep=DIRECTORY_SEPARATOR;
if(is_dir(".{$sep}xml")){
return".{$sep}xml{$sep}{$dictionary->getType()}.$ext";
}
returnparent::path($dictionary,$ext);
}
//...
可以看到,该方法检查本地xml目录。如果该测试失败,则它使用parent关键字指派给父方法。
子类和构造函数方法
parent关键字在构造函数方法中尤其重要。如果在子类中不定义构造函数,则parent构造函数代表您被显式调用。如果在子类中不创建构造函数方法。则调用父类的构造函数并传递任何参数是您的责任,如清单4所示:
Listing4.Invokingtheparentclass’sconstructor
classSpecialDictionaryextendsDictionary{
function__construct($type,DictionaryIO$dictio,$additional){
//dosomethingwith$additional
parent::__construct($type,$dictio);
}
}
抽象类和方法
虽然在父类中提供默认行为是完全合法的,但这可能不是最巧妙的方法。对于启动器,您必须依赖子类的作者来理解它们必须实现import()和export(),才能在broken状态创建类。而且,DictionaryIO类实际上是兄弟,而不是父子。XmlDictionaryIO不是DictionaryIO的特例;相反,它是一种备选实现。
PHPV5允许定义部分实现的类,其主要角色是为它的子女指定核心接口。这种类必须声明为抽象。
abstractclassDictionaryIO{}
抽象类不能实例化。必须创建子类(即,创建继承它的类),并创建该子类的实例。可以在抽象类中声明标准和抽象方法,如清单5所示。抽象方法必须用abstract关键字限定,且必须只由一个方法签名组成。这意味着,抽象方法应包括abstract关键字、可选的可见度修改符、function关键字,以及圆括号内可选的参数列表。它们不应有任何方法主体。
清单5.声明抽象类
abstractclassDictionaryIO{
protectedfunctionpath(Dictionary$dictionary,
$ext){
$path=Dictionary::getSaveDirectory();
$path.=DIRECTORY_SEPARATOR;
$path.=$dictionary->getType().".$ext";
return$path;
}
abstractfunctionimport(Dictionary$dictionary);
abstractfunctionexport(Dictionary$dictionary);
}
注意,path()函数现在是受保护的。这允许来自子类的访问,但不允许来自DictionaryIO类型外部的访问。继承DictionaryIO的任何类必须实现import()和export()方法,否则就可能得到致命错误。
声明抽象方法的任何类本身必须是声明为抽象的。继承抽象类的子类必须实现在其父类或自身中声明为抽象的所有抽象方法。
清单6展示了具体的DictionaryIO类,为了简洁,此处省略了实际实现。
清单6.具体的DictionaryIO类
classSerialDictionaryIOextendsDictionaryIO{
functionexport(Dictionary$dictionary){
//implementation
}
functionimport(Dictionary$dictionary){
//implementation
}
}
classXmlDictionaryIOextendsDictionaryIO{
protectedfunctionpath(Dictionary$dictionary,$ext){
$path=strtolower(parent::path($dictionary,$ext));
return$path;
}
functionexport(Dictionary$dictionary){
//implementation
}
functionimport(Dictionary$dictionary){
//implementation
}
}
Dictionary类需要一个DictionaryIO对象传递到它的构造函数,但它既不知道也不关心该对象是否是XmlDictionaryIO或SerialDictionaryIO的实例。它惟一知道的是给定对象继承DictionaryIO,而且因此可以保证支持import()和export()方法。这种在运行时的类切换是面向对象编程的一个常见特性,称为多态性。
图2展示了DictionaryIO类。注意,抽象类和抽象方法用斜体表示。该图是多态性的一个好例子。它展示了DictionaryIO类的已定义关系是与DictionaryIO,但SerialDictionaryIO或XmlDictionaryIO将实现该关系。
接口
与Java?编程语言应用程序一样,PHP只支持单一继承。这意味着,类只可以继承一个父类(虽然它可能间接地继承许多祖先)。虽然这保证了清洁设计(cleandesign),但有时候您可能需要为一个类定义多个能力集。
使用对象的一个优点是类型可以为您提供功能的保证。Dictionary对象总是具有get()方法,而不管它是Dictionary本身还是其子类的实例。Dictionary的另一个特性是它对export()的支持。假设需要让系统中的大量其他类同样地可导出。当想要将系统的状态保存到文件中时,可以为这些完全不同的类提供各自的export()方法,然后聚集实例,循环通过所有实例,并为每个实例调用export()。清单7展示了实现export()方法的第二个类。
清单7.实现export()方法的第二个类
评论 (0) All