唐山网站建设

设为主页 加入收藏 繁體中文

高级PHP V5 对象研究

核心提示:PHP编程,PHP教程,PHP5教程,PHP开发

高级PHPV5对象研究
本文先容了PHPV51些更高级的面向设计的特性。其中包括各种对象类型,它们答应将系统中的组件相互分离,创建可重用、可扩大、可伸缩的代码。


领会暗示

首先先容1下对象类型和类型提示的优点。1个类定义1种类型。从该类实例化的任何对象属于该类定义的类型。所以,使用Car类创建Car对象。假设Car类继续Vehicle超类,则Car对象还将是1个Vehicle对象。这反应了我们在现实世界中分类事物的方法。但正如您将看到的,类型不单单是分类系统元素的有用方法。类型是面向对象编程的基础,由于类型是良好1致的行动的保证。很多设计技能来自该保证。

“开始了解PHPV5中的对象”展现对象为您保证了接口。当系统传递Dictionary对象时,您可以肯定它具有$translations数组和summarize()方法。相反,关联数组不提供相同级别的肯定性。要利用类提供的清楚接口,需要知道您的对象实际上是Dictionary的1个实例,而不是某个imposter。可以用instanceof操纵符来手动验证这1点,该操纵符是PHPV5引进的介于对象实例和类名之间的1个便捷工具。

instanceofDictionary

假设给定对象是给定类的实例,则instanceof操纵符解析为真。在调用方法中第1次碰到Dictionary对象时,可以在使用它之前检查它的类型。

if($eninstanceofDictionary){
 print$en->summarize();
}

但是,假设使用PHPV5的话,可以将对象类型检查构建到类或方法声明中。

在“开始了解PHPV5中的对象”中,重点先容两个类:Dictionary,它存储术语和翻译,DictionaryIO,它将Dictionary数据导出(导进)自(至)文件系统。这些特性使得将Dictionary文件发送到第3方翻译器变得轻易,第3方翻译器可使用自己的软件来编辑数据。然后,您可以重新导进已处理的文件。清单1是Dictionary类的1个版本,它接受1个DictionaryIO对象,并将其存储以备将来使用。

清单1.接受DictionaryIO对象的Dictionary类的1个版本

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(),它接受1个Dictionary对象,并使用它来输出假消息。现在,Dictionary具有两个新方法:addDictionaryIO(),接受并存储DictionaryIO对象;export(),使用已提供的对象导出Dictionary数据——或是在完全实现的版本中。

您可能会迷惑为甚么Dictionary对象不但实例化自己的DictionaryIO对象,或乃至在内部处理导进导出操纵,而根本不求助于第2个对象。1个缘由是您可能希看1个DictionaryIO对象使用多个Dictionary对象,或希看存储该对象的单独援用。另1个缘由是通过将DictionaryIO对象传递给Dictionary,可以利用类切换或多态性。换句话说,可以将DictionaryIO子类(比如XmlDictionaryIO)的实例传递给Dictionary,并更改运行时保存和检索数据的方法。

图1显示了Dictionary和DictionaryIO类及其使用关系。

正如所显示的,没有甚么禁止编码器将完全随机的对象传递给addDictionaryIO()。只有在运行export()时,才会取得1个类似的毛病,并发现已存储在$dictio中的对象实际上并没有export()方法。使用PHPV4时,必须测试本例中的参数类型,以尽对确保编码器传递类型正确的对象。使用PHPV5时,可以部署参数提示来强迫对象类型。只将所需的对象类型添加到方法声明的参数变量中,如清单2所示:

清单2.将对象类型添加到方法声明的参数变量中

functionaddDictionaryIO(DictionaryIO$dictio){
 $this->dictio=$dictio;
}

functionexport(){
 if($this->dictio){
$this->dictio->export($this);
 }
}

现在,假设客户机编码器试图将类型毛病的对象传递给addDictionaryIO(),PHP引擎将抛出1个致命毛病。因此,类型提示使得代码更安全。不幸的是,提示仅对对象有效,所以不能在参数列表中要求字符串或整数。必须手动测试这些原类型。

即使可以保证addDictionaryIO()将取得正确的对象类型,但不能保证该方法被首先调用。export()方法测试export()方法中$dictio属性的存在,从而避免毛病。但您可能希看更严格1些,要求DictionaryIO对象传递给构造函数,从而确保$dictio总是被填充。

调用覆盖方法

在清单3中,XmlDictionaryIO集成DictionaryIO。而DictionaryIO写进并读取序列化数据,XmlDictionaryIO操纵XML,可以与第3方利用程序共享。XmlDictionaryIO可以覆盖其父方法(import()和export()),也能够选择不提供自己的实现(path())。假设客户机调用XmlDictionaryIO对象中的path()方法,则在DictionaryIO中实现的path()方法被调用。

事实上,可以同时使用这两种方法。可以覆盖方法并调用父实现。为此,使用新关键字parent。用范围解析操纵符和所讨论方法的名称来使用parent。例如,假定需要XmlDictionaryIO使用当前工作目录(假设有1个可用)中叫做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关键字指派给父方法。

#p#分页标题#e#

子类和构造函数方法

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的特例;相反,它是1种备选实现。

PHPV5答应定义部份实现的类,其主要角色是为它的子女指定核心接口。这类类必须声明为抽象。


abstractclassDictionaryIO{}


抽象类不能实例化。必须创建子类(即,创建继续它的类),并创建该子类的实例。可以在抽象类中声明标准和抽象方法,如清单5所示。抽象方法必须用abstract关键字限定,且必须只由1个方法签名组成。这意味着,抽象方法应包括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类需要1个DictionaryIO对象传递到它的构造函数,但它既不知道也不关心该对象是否是是XmlDictionaryIO或SerialDictionaryIO的实例。它惟1知道的是给定对象继续DictionaryIO,而且因此可以保证支持import()和export()方法。这类在运行时的类切换是面向对象编程的1个常见特性,称为多态性。

图2展现了DictionaryIO类。留意,抽象类和抽象方法用斜体表示。该图是多态性的1个好例子。它展现了DictionaryIO类的已定义关系是与DictionaryIO,但SerialDictionaryIO或XmlDictionaryIO将实现该关系。


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


接口

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

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

#p#分页标题#e#

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


classThirdPartyNews{
//...
}

classOurNew***tendsThirdPartyNews{
//...
functionexport(){
print"OurNew***port\n";
}
}


留意,本例包括束缚,即新类OurNews继续1个叫做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()方法签名中的1些类型提示。Dictionary和OurNews专用于不同的根。您可以依托add()方法内部的类型检查,但这其实不优雅而且不固定。每次创建支持export()的新类型时,就需要创建1个新类型检查。

接口免往了这类麻烦。正如名称所表明的,接口定义功能而非实现。用inte***ce关键字声明接口。


inte***ceExportable{
publicfunctionexport();
}


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

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


classOurNew***tendsThirdPartyNews
implementsExportable{
 //...
 functionexport(){
print"OurNew***port\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;
 }
//...


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

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

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

唐山网站建设www.fw8.net


TAG:方法,对象,类型,子类,清单
评论加载中...
内容:
评论者: 验证码: