明輝手游網中心:是一個免費提供流行視頻軟件教程、在線學習分享的學習平臺!

高級PHP V5 對象研究

[摘要]高級PHP V5 對象研究 本文介紹了PHP V5一些更高級的面向設計的特性。其中包括各種對象類型,它們允許將系統(tǒng)中的組件相互分離,創(chuàng)建可重用、可擴展、可伸縮的代碼。   領會暗示   首先介紹一下對象類型和類型提示的優(yōu)點。一個類定義一種類型。從該類實例化的任何對象屬于該類定義的類型。所以,使用...

高級PHP V5 對象研究
本文介紹了PHP V5一些更高級的面向設計的特性。其中包括各種對象類型,它們允許將系統(tǒng)中的組件相互分離,創(chuàng)建可重用、可擴展、可伸縮的代碼。


  領會暗示 

  首先介紹一下對象類型和類型提示的優(yōu)點。一個類定義一種類型。從該類實例化的任何對象屬于該類定義的類型。所以,使用 Car 類創(chuàng)建 Car 對象。如果 Car 類繼承 Vehicle 超類,則 Car 對象還將是一個 Vehicle 對象。這反映了我們在現實世界中分類事物的方法。但正如您將看到的,類型不僅僅是分類系統(tǒng)元素的有用方法。類型是面向對象編程的基礎,因為類型是良好一致的行為的保證。許多設計技巧來自該保證。

  “開始了解 PHP V5 中的對象”展示對象為您保證了接口。當系統(tǒng)傳遞 Dictionary 對象時,您可以確定它具有 $translations 數組和 summarize() 方法。相反,關聯(lián)數組不提供相同級別的確定性。要利用類提供的清晰接口,需要知道您的對象實際上是 Dictionary 的一個實例,而不是某個 imposter?梢杂 instanceof 操作符來手動驗證這一點,該操作符是 PHP V5 引入的介于對象實例和類名之間的一個便捷工具。

   instanceof Dictionary

  如果給定對象是給定類的實例,則 instanceof 操作符解析為真。在調用方法中第一次遇到 Dictionary 對象時,可以在使用它之前檢查它的類型。

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

  但是,如果使用 PHP V5 的話,可以將對象類型檢查構建到類或方法聲明中。

  在“開始了解 PHP V5 中的對象”中,重點介紹兩個類:Dictionary,它存儲術語和翻譯, DictionaryIO,它將 Dictionary 數據導出(導入)自(至)文件系統(tǒng)。這些特性使得將 Dictionary 文件發(fā)送到第三方翻譯器變得容易,第三方翻譯器可以使用自己的軟件來編輯數據。然后,您可以重新導入已處理的文件。清單 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 對象,并使用它來輸出假消息,F在,Dictionary 具有兩個新方法:addDictionaryIO(),接受并存儲 DictionaryIO 對象; export(),使用已提供的對象導出 Dictionary 數據 —— 或者是在完全實現的版本中。

  您可能會疑惑為什么 Dictionary 對象不僅實例化自己的 DictionaryIO 對象,或者甚至在內部處理導入導出操作,而根本不求助于第二個對象。一個原因是您可能希望一個 DictionaryIO 對象使用多個 Dictionary 對象,或者希望存儲該對象的單獨引用。另一個原因是通過將 DictionaryIO 對象傳遞給 Dictionary,可以利用類切換或 多態(tài)性。換句話說,可以將 DictionaryIO 子類(比如 XmlDictionaryIO)的實例傳遞給 Dictionary,并更改運行時保存和檢索數據的方法。

  圖 1 顯示了 Dictionary 和 DictionaryIO 類及其使用關系。

高級PHP V5 對象研究

正如所顯示的,沒有什么阻止編碼器將完全隨機的對象傳遞給 addDictionaryIO()。只有在運行 export() 時,才會獲得一個類似的錯誤,并發(fā)現已經存儲在 $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() 方法被調用。

  事實上,可以同時使用這兩種方法?梢愿采w方法并調用父實現。為此,使用新關鍵字 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 關鍵字指派給父方法。


[page_break]

子類和構造函數方法

  parent 關鍵字在構造函數方法中尤其重要。如果在子類中不定義構造函數,則 parent 構造函數代表您被顯式調用。如果在子類中不創(chuàng)建構造函數方法。則調用父類的構造函數并傳遞任何參數是您的責任,如清單 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 );
 }
}


抽象類和方法

  雖然在父類中提供默認行為是完全合法的,但這可能不是最巧妙的方法。對于啟動器,您必須依賴子類的作者來理解它們必須實現 import() 和 export(),才能在 broken 狀態(tài)創(chuàng)建類。而且,DictionaryIO 類實際上是兄弟,而不是父子。XmlDictionaryIO 不是 DictionaryIO 的特例;相反,它是一種備選實現。

  PHP V5 允許定義部分實現的類,其主要角色是為它的子女指定核心接口。這種類必須聲明為抽象。


abstract class DictionaryIO {}


  抽象類不能實例化。必須創(chuàng)建子類(即,創(chuàng)建繼承它的類),并創(chuàng)建該子類的實例?梢栽诔橄箢愔新暶鳂藴屎统橄蠓椒ǎ缜鍐 5 所示。抽象方法必須用 abstract 關鍵字限定,且必須只由一個方法簽名組成。這意味著,抽象方法應包括 abstract 關鍵字、可選的可見度修改符、function 關鍵字,以及圓括號內可選的參數列表。它們不應有任何方法主體。

  清單 5. 聲明抽象類


abstract class DictionaryIO {

protected function path( Dictionary $dictionary, 
$ext ) {
$path = Dictionary::getSaveDirectory();
$path .= DIRECTORY_SEPARATOR;
$path .= $dictionary->getType().".$ext";
return $path;
}

abstract function import( Dictionary $dictionary ); 
abstract function export( Dictionary $dictionary ); 
}


  注意,path() 函數現在是受保護的。這允許來自子類的訪問,但不允許來自 DictionaryIO 類型外部的訪問。繼承 DictionaryIO 的任何類必須實現 import() 和 export() 方法,否則就可能得到致命錯誤。

  聲明抽象方法的任何類本身必須是聲明為抽象的。繼承抽象類的子類必須實現在其父類或自身中聲明為抽象的所有抽象方法。

  清單 6 展示了具體的 DictionaryIO 類,為了簡潔,此處省略了實際實現。

  清單 6. 具體的 DictionaryIO 類


class SerialDictionaryIO extends DictionaryIO {

 function export( Dictionary $dictionary ) {
  // implementation
 }

 function import( Dictionary $dictionary ) {
  // implementation
 }
}

class XmlDictionaryIO extends DictionaryIO {

 protected function path( Dictionary $dictionary, $ext ) {
  $path = strtolower(parent::path( $dictionary, $ext ) ); 
  return $path;
 }

 function export( Dictionary $dictionary ) {
  // implementation
 }

 function import( Dictionary $dictionary ) {
  // implementation
 }
}


  Dictionary 類需要一個 DictionaryIO 對象傳遞到它的構造函數,但它既不知道也不關心該對象是否是 XmlDictionaryIO 或 SerialDictionaryIO 的實例。它惟一知道的是給定對象繼承 DictionaryIO,而且因此可以保證支持 import() 和 export() 方法。這種在運行時的類切換是面向對象編程的一個常見特性,稱為多態(tài)性。

  圖 2 展示了 DictionaryIO 類。注意,抽象類和抽象方法用斜體表示。該圖是多態(tài)性的一個好例子。它展示了 DictionaryIO 類的已定義關系是與 DictionaryIO,但 SerialDictionaryIO 或 XmlDictionaryIO 將實現該關系。

高級PHP V5 對象研究
圖 2. 抽象 DictionaryIO 類及其具體子類 


接口

  與 Java? 編程語言應用程序一樣,PHP 只支持單一繼承。這意味著,類只可以繼承一個父類(雖然它可能間接地繼承許多祖先)。雖然這保證了清潔設計(clean design),但有時候您可能需要為一個類定義多個能力集。

  使用對象的一個優(yōu)點是類型可以為您提供功能的保證。Dictionary 對象總是具有 get() 方法,而不管它是 Dictionary 本身還是其子類的實例。Dictionary 的另一個特性是它對 export() 的支持。假設需要讓系統(tǒng)中的大量其他類同樣地可導出。當想要將系統(tǒng)的狀態(tài)保存到文件中時,可以為這些完全不同的類提供各自的 export() 方法,然后聚集實例,循環(huán)通過所有實例,并為每個實例調用 export()。清單 7 展示了實現 export() 方法的第二個類。


[page_break]清單 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(),循環(huán)通過已存儲對象,以對每個對象調用 export()。這種設計的缺點顯而易見:add() 不檢查所提供對象的類型,所以 exportAll() 在輕快地調用 export() 時冒了致命的風險。 此處真正有用的是 add() 方法簽名中的一些類型提示。Dictionary 和 OurNews 專用于不同的根。您可以依賴 add() 方法內部的類型檢查,但這并不優(yōu)雅而且不固定。每次創(chuàng)建支持 export() 的新類型時,就需要創(chuàng)建一個新類型檢查。

  接口免去了這種麻煩。正如名稱所表明的,接口定義功能而非實現。用 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() 時發(fā)生的行為。它只知道它可以安全地調用該方法。

  圖 3 顯示了 Exportable 接口與其實現類之間的關系。注意到 Exporter 與 Exportable 接口而非具體實現有使用關系。接口關系用虛線和開箭頭表示。

高級PHP V5 對象研究

結束語
  本文支持使用 PHP V5 中類型的價值。對象類型允許將系統(tǒng)中的組件相互分離,從而得到可重用、可擴展和可伸縮的代碼。抽象類和接口幫助您基于類類型設計系統(tǒng)。 客戶機類可被編碼為只需要抽象類型,而把實現策略和結果留給在運行時傳遞給它們的具體類實例。也就是說,Dictionary 既不局限于序列化數據,也不局限于 XML。如果必須支持一種新格式,Dictionary 將不需要任何進一步的開發(fā)。它與保存數據以及從文件系統(tǒng)加載數據和將數據加載到文件系統(tǒng)的機制完全無關。Dictionary 只知道它必須具有一個 DictionaryIO 對象,從而保證 export() 和 import() 的功能。
  如果類保證了接口,您必須能夠保證類。雖然 instanceof 功能提供了一種檢查類型的好方法,但您還可以通過在參數列表中使用類型提示,來將對象類型檢查滾動到方法簽名自身中