当前位置: 首页 > 图文教程 > 网络编程 > PHP > 高级PHP V5 对象研究

PHP
《PHP设计模式介绍》第十三章 适配器模式
《PHP设计模式介绍》第十四章 动态记录模式
《PHP设计模式介绍》第十五章 表数据网关模式
《PHP设计模式介绍》第十六章 数据映射模式
《PHP设计模式介绍》第十七章 MVC 模式
Zend Framework 入门——快速上手
Zend Framework 入门——多国语言支持
Zend Framework 入门——错误处理
Zend Framework 入门——页面布局
详细介绍php5编程中的异常处理
PHP5 OOP编程中的代理与异常
PHP程序的常见漏洞攻击分析
PHP.MVC的模板标签系统
PHP教程:PHP编码书写规范
PHP开发大型项目的方法:OOP思想
php使用curl模拟用户登陆
php对gb编码动态转utf-8编码的几种方法评测
php设计模式介绍之章代理模式
“在phpMyAdmin使用用户口令登陆”补充
PHP入门速成

高级PHP V5 对象研究


出处:互联网   整理: 软晨网(RuanChen.com)   发布: 2009-09-23   浏览: 96 ::
收藏到网摘: n/a

高级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将实现该关系。


图2.抽象DictionaryIO类及其具体子类


接口

与Java?编程语言应用程序一样,PHP只支持单一继承。这意味着,类只可以继承一个父类(虽然它可能间接地继承许多祖先)。虽然这保证了清洁设计(cleandesign),但有时候您可能需要为一个类定义多个能力集。

使用对象的一个优点是类型可以为您提供功能的保证。Dictionary对象总是具有get()方法,而不管它是Dictionary本身还是其子类的实例。Dictionary的另一个特性是它对export()的支持。假设需要让系统中的大量其他类同样地可导出。当想要将系统的状态保存到文件中时,可以为这些完全不同的类提供各自的export()方法,然后聚集实例,循环通过所有实例,并为每个实例调用export()。清单7展示了实现export()方法的第二个类。

清单7.实现export()方法的第二个类


classThirdPartyNews{
//...
}

classOurNewsextendsThirdPartyNews{
//...
functionexport(){
print"OurNewsexport\n";
}
}


注意,本例包括约束,即新类OurNews继承一个叫做ThirdPartyNews的外部类。

清单8展示了聚集用export()方法装备的类实例的类。

清单8.聚集用export()方法装备的类实例的类


classExporter{
 private$exportable=array();
 functionadd($obj){
$this->exportable[]=$obj;
 }

 functionexportAll(){
foreach($this->exportableas$obj){
 $obj->export();
}
 }
}


Exporter类定义了两个方法:add(),接受要存储的对象,和exportAll(),循环通过已存储对象,以对每个对象调用export()。这种设计的缺点显而易见:add()不检查所提供对象的类型,所以exportAll()在轻快地调用export()时冒了致命的风险。此处真正有用的是add()方法签名中的一些类型提示。Dictionary和OurNews专用于不同的根。您可以依赖add()方法内部的类型检查,但这并不优雅而且不固定。每次创建支持export()的新类型时,就需要创建一个新类型检查。

接口免去了这种麻烦。正如名称所表明的,接口定义功能而非实现。用interface关键字声明接口。


interfaceExportable{
publicfunctionexport();
}


对于抽象类,可以定义任意数目的方法签名。子类必须提供每个方法的实现。但是,与抽象类不同,接口完全不能包含任何具体方法(也就是说,任何方法的任何特性都不能与其签名分离)。类用implements关键字实现接口,如清单9所示。

清单9.用implements关键字实现接口的类


classOurNewsextendsThirdPartyNews
implementsExportable{
 //...
 functionexport(){
print"OurNewsexport\n";
 }
}

classDictionaryimplementsExportable,Iterator{
 functionexport(){
//...
 }
}


通过在implements后使用逗号分隔的列表,可以实现任意多的接口。必须实现每个接口中声明的所有方法,或者声明您的实现类抽象。这样做可以得到什么呢?现在,Dictionary和OurNews对象共享类型。所有此类对象还是Exportable。可以用类型提示和instanceof测试来检查它们。清单10展示了修改后的Exporter::add()方法。

清单10.修改后的Exporter::add()方法


classExporter{
 private$exportable=array();
 functionadd(Exportable$obj){
$this->exportable[]=$obj;
 }
//...


接口是一个难以掌握的概念。毕竟,它们实际上并不提供任何有用代码。窍门是记住面向对象编程中类型的重要性。接口与合同类似。它借给类一个将类放置到位的名称,反过来,该类保证特定方法将可用。此外,使用Exportable对象的类既不知道也不关心调用export()时发生的行为。它只知道它可以安全地调用该方法。

图3显示了Exportable接口与其实现类之间的关系。注意到Exporter与Exportable接口而非具体实现有使用关系。接口关系用虚线和开箭头表示。

结束语
本文支持使用PHPV5中类型的价值。对象类型允许将系统中的组件相互分离,从而得到可重用、可扩展和可伸缩的代码。抽象类和接口帮助您基于类类型设计系统。客户机类可被编码为只需要抽象类型,而把实现策略和结果留给在运行时传递给它们的具体类实例。也就是说,Dictionary既不局限于序列化数据,也不局限于XML。如果必须支持一种新格式,Dictionary将不需要任何进一步的开发。它与保存数据以及从文件系统加载数据和将数据加载到文件系统的机制完全无关。Dictionary只知道它必须具有一个DictionaryIO对象,从而保证export()和import()的功能。
如果类保证了接口,您必须能够保证类。虽然instanceof功能提供了一种检查类型的好方法,但您还可以通过在参数列表中使用类型提示,来将对象类型检查滚动到方法签名自身中