オブジェクト高度な
高级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)\n";
}
}
$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 类及其使用关系。
正如所显示的,没有什么阻止编码器将完全随机的对象传递给 addDictionaryIO()。只有在运行 export() 时,才会获得一个类似的错误,并发现已经存储在 $dictio 中的对象实际上并没有 export() 方法。使用 PHP V4 时,必须测试本例中的参数类型,以绝对确保编码器传递类型正确的对象。使用 PHP V5 时,可以部署参数提示来强制对象类型。只将所需的对象类型添加到方法声明的参数变量中,如清单 2 所示:
清单 2. 将对象类型添加到方法声明的参数变量中
function addDictionaryIO( DictionaryIO $dictio ) {
$this->dictio=$dictio;
}
function export() {
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 类生成的默认路径
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 );
}
// ...
可以看到,该方法检查本地 xml 目录。如果该测试失败,则它使用 parent 关键字指派给父方法。
サブクラスとコンストラクターメソッド
コンストラクターメソッドでは、parentキーワードが特に重要です。子クラスでコンストラクターを定義しない場合は、代わりに親コンストラクターが明示的に呼び出されます。サブクラスにコンストラクタメソッドが作成されていない場合。次に、リスト 4 に示すように、親クラスのコンストラクターを呼び出してパラメーターを渡すのはユーザーの責任です。
リスト 4. 親クラスのコンストラクターの呼び出し
class SpecialDictionary extends Dictionary {
function __construct( $type, DictionaryIO $dictio, $Additional ですが、これは最も適切なアプローチではない可能性があります。まず、サブクラスの作成者に頼って、壊れた状態でクラスを作成するには import() と export() を実装する必要があることを理解する必要があります。また、DictionaryIO クラスは実際には親子ではなく兄弟です。 XmlDictionaryIO は DictionaryIO の特殊なケースではなく、代替実装です。
PHP V5 では部分的に実装されたクラスの定義が可能で、その主な役割はその子のコアインターフェイスを指定することです。このようなクラスは抽象として宣言する必要があります。
abstract class DictionaryIO {}
抽象クラスはインスタンス化できません。サブクラス化 (つまり、それを継承するクラスの作成) し、そのサブクラスのインスタンスを作成する必要があります。リスト 5 に示すように、標準メソッドと抽象メソッドは抽象クラスで宣言できます。抽象メソッドは、abstract キーワードで修飾する必要があり、1 つのメソッド シグネチャのみで構成されている必要があります。これは、抽象メソッドには、abstract キーワード、オプションの可視性修飾子、関数キーワード、およびかっこ内のオプションのパラメーター リストを含める必要があることを意味します。メソッド本体を持たないでください。
リスト5. 抽象クラスの宣言
abstract class DictionaryIO {
protected function path(Dictionary $dictionary,
$ext) {
$path = Dictionary::getSaveDirectory() .= DIRECTORY_SEPARATOR ; path .= $dictionary->getType().".$ext";
return $path;
abstract function import( Dictionary $dictionary );
path()関数が保護されるようになったことに注意してください。これにより、サブクラスからのアクセスは許可されますが、DictionaryIO 型の外部からのアクセスは許可されません。 DictionaryIO を継承するクラスは、import() メソッドと export() メソッドを実装する必要があります。実装しないと、致命的なエラーが発生する可能性があります。
抽象メソッドを宣言するクラスは、それ自体が抽象メソッドとして宣言されなければなりません。抽象クラスを継承するサブクラスは、その親クラスまたはそれ自体で抽象として宣言されたすべての抽象メソッドを実装する必要があります。
リスト 6 は、具体的な DictionaryIO クラスを示しています。簡単にするために、ここでは実際の実装は省略しています。
リスト6. 具体的な DictionaryIO クラス
class SerialDictionaryIO extends DictionaryIO {
function export( Dictionary $dictionary ) {
// 実装
}
function import( Dictionary $dictionary ) {
// 実装
}
}
class
function export( Dictionary $dictionary ) {
// 実装
}
function import( Dictionary $dictionary ) {
// 実装
}
}
Dictionary クラスには DictionaryIO オブジェクトを渡す必要がありますただし、オブジェクトが XmlDictionaryIO または SerialDictionaryIO のインスタンスであるかどうかは認識も考慮もされません。唯一わかっていることは、指定されたオブジェクトが DictionaryIO を継承しているため、import() メソッドと export() メソッドをサポートすることが保証されていることです。実行時のこの種のクラス切り替えは、オブジェクト指向プログラミングの一般的な機能であり、ポリモーフィズムと呼ばれます。
图 2 展示了 DictionaryIO 类。注意,抽象类和抽象方法用斜体表示。该图是多态性的一个好例子。它展示了 DictionaryIO 类的已定义关系是与 DictionaryIO,但 SerialDictionaryIO 或 XmlDictionaryIO 将实现该关系。
图 2. 抽象 DictionaryIO 类及其具体子类
接口
与 Java? 编程语言应用程序一样,PHP 只支持单一继承。这意味着,类只可以继承一个父类(虽然它可能间接地继承许多祖先)。虽然这保证了清洁设计(clean design),但有时候您可能需要为一个类定义多个能力集。
使用对象的一个优点是类型可以为您提供功能的保证。Dictionary 对象总是具有 get() 方法,而不管它是 Dictionary 本身还是其子类的实例。Dictionary 的另一个特性是它对 export() 的支持。假设需要让系统中的大量其他类同样地可导出。当想要将系统的状态保存到文件中时,可以为这些完全不同的类提供各自的 export() 方法,然后聚集实例,循环通过所有实例,并为每个实例调用 export()。清单 7 展示了实现 export() 方法的第二个类。
清单 7. 实现 export() 方法的第二个类
class ThirdPartyNews {
// ...
}
class OurNews extends ThirdPartyNews {
// ...
function export() {
print "OurNews export\n";
}
}
注意,本例包括约束,即新类 OurNews 继承一个叫做 ThirdPartyNews 的外部类。
清单 8 展示了聚集用 export() 方法装备的类实例的类。
清单 8. 聚集用 export() 方法装备的类实例的类
class Exporter {
private $exportable = array();
function add( $obj ) {
$this->exportable[] = $obj;
}
function exportAll() {
foreach ( $this->exportable as $obj ) {
$obj->export();
}
}
}
Exporter 类定义了两个方法:add(),接受要存储的对象,和 exportAll(),循环通过已存储对象,以对每个对象调用 export()。这种设计的缺点显而易见:add() 不检查所提供对象的类型,所以 exportAll() 在轻快地调用 export() 时冒了致命的风险。 此处真正有用的是 add() 方法签名中的一些类型提示。Dictionary 和 OurNews 专用于不同的根。您可以依赖 add() 方法内部的类型检查,但这并不优雅而且不固定。每次创建支持 export() 的新类型时,就需要创建一个新类型检查。
接口免去了这种麻烦。正如名称所表明的,接口定义功能而非实现。用 interface 关键字声明接口。
interface Exportable {
public function export();
}
对于抽象类,可以定义任意数目的方法签名。子类必须提供每个方法的实现。但是,与抽象类不同,接口完全不能包含任何具体方法(也就是说,任何方法的任何特性都不能与其签名分离)。 类用 implements 关键字实现接口,如清单 9 所示。
清单 9. 用 implements 关键字实现接口的类
class OurNews extends ThirdPartyNews
implements Exportable {
// ...
function export() {
print "OurNews export\n";
}
}
class Dictionary implements Exportable, Iterator {
function export() {
//...
}
}
通过在 implements 后使用逗号分隔的列表,可以实现任意多的接口。必须实现每个接口中声明的所有方法,或者声明您的实现类抽象。 这样做可以得到什么呢?现在,Dictionary 和 OurNews 对象共享类型。所有此类对象还是 Exportable。可以用类型提示和 instanceof 测试来检查它们。清单 10 展示了修改后的 Exporter::add() 方法。
清单 10. 修改后的 Exporter::add() 方法
class Exporter {
private $exportable = array();
function add( Exportable $obj ) {
$this->exportable[] = $obj;
}
//...
接口是一个难以掌握的概念。毕竟,它们实际上并不提供任何有用代码。窍门是记住面向对象编程中类型的重要性。接口与合同类似。它借给类一个将类放置到位的名称,反过来,该类保证特定方法将可用。此外,使用 Exportable 对象的类既不知道也不关心调用 export() 时发生的行为。它只知道它可以安全地调用该方法。
图 3 显示了 Exportable 接口与其实现类之间的关系。注意到 Exporter 与 Exportable 接口而非具体实现有使用关系。接口关系用虚线和开箭头表示。
結論
この記事は、PHP V5 での型の値の使用をサポートします。オブジェクト タイプを使用すると、システム内のコンポーネントを相互に分離できるため、再利用可能、拡張可能、スケーラブルなコードが得られます。抽象クラスとインターフェイスは、クラス タイプに基づいてシステムを設計するのに役立ちます。クライアント クラスは、抽象型のみを必要とするようにコード化し、実装戦略と結果は実行時にクライアント クラスに渡される具体的なクラス インスタンスに任せることができます。つまり、Dictionary はシリアル化されたデータや XML に限定されません。新しい形式をサポートする必要がある場合でも、Dictionary をさらに開発する必要はありません。これは、ファイル システムへのデータの保存やファイル システムへのデータのロードの仕組みとはまったく関係がありません。 Dictionary は、export() と import() の機能を保証するために DictionaryIO オブジェクトが必要であることだけを知っています。
クラスがインターフェースを保証するなら、そのクラスも保証できなければなりません。 instanceof 関数は型をチェックする優れた方法を提供しますが、パラメーター リストの型ヒントを使用してオブジェクト型チェックをメソッド シグネチャ自体に組み込むこともできます