摘要:本文將討論多態性的概念及其在物件導向設計中的應用,也將分析如何在PHP 5中使用多態性以及存在的優缺點。
PHP的最新發行版本中已經實現了對遲綁定的支援。當然,在使用其遲綁定功能時仍存在許多問題。如果你使用的是較舊版本的PHP(我的伺服器上執行的是PHP 5.0.1版本),那麼你可能會發現其中缺乏對於遲綁定的支援。因此,請注意本文中的程式碼有可能無法運作在你特定的PHP 5版本中。
一、 PHP 5和多態性
本文想討論物件導向程式設計中最重要的部分之一--多態性的設計。為了說明問題,我使用了PHP 5。在你繼續閱讀之前,請先先明確本文並不是完全有關於PHP的。儘管這種語言在以前的兩個主要版本中在快速開發方面已經取得很大的進步,但是,在其與更為成熟的語言如C++或者Java相匹敵之前,它對於對象的支持還要經歷一段歷程。
如果你是一位物件導向程式設計的入門者,那麼本文可能不適合你,因為多態性這部分知識比較特別:一旦理解了它,你將永遠不會忘記。如果你想簡單了解一點物件程式設計和設計知識,並且當某人說"某個物件是多態的"時,還不十分清楚這是什麼意思的話,那麼本文正適合你。
到本文最後,你應該知道什麼是多態性以及如何把它應用到物件導向的設計中,你會了解PHP 5中物件程式設計的優點與不足。
二、什麼是多態性?
多態性,其來自於dictionary.com的定義是"以不同形式,階段或類型出現在獨立的組織中或同種組織中,而不存在根本區別。"由該定義,我們可以認為,多態性是一種透過多種狀態或階段來描述相同物件的程式設計方式。其實,它的真正意義在於:實際開發中,我們只需要專注於一個介面或基類的編程,而不必擔心一個物件所屬於的特定類別(class)。
如果你熟悉設計模式,即使只是有初步了解,那麼你也會了解這個概念。事實上,多態性可能是基於模式設計程式設計中的最偉大的工具。它允許我們以一種邏輯的方式來組織相類似的對象從而實現在具體編碼時不必擔心對象的具體類型;而且,我們只需要對一個所期望的接口或基類編程即可。一個應用程式越抽象,它就顯得越靈活--而多態性是對行為加以抽象的最好的方式之一。
例如,讓我們考慮一個叫Person的類別。我們可以用稱為David,Charles和Alejandro的類別來子類化Person。 Person有一個抽象方法AcceptFeedback(),所有的子類別都要實作這個方法。這意味著,任何使用基底類別Person的子類別的程式碼都能呼叫方法AcceptFeedback()。你不必檢查該物件是一個David還是一個Alejandro,只知道它是一個Person就夠了。結果是,你的程式碼只需要關注"最小公分母"-Person類即可。
在這個範例中的Person類別也可以被建立為一個介面。當然,與上面相比存在一些區別,主要在於:一個介面並沒有給出任何行為,而僅確定了一組規則。 Person介面要求的是"你必須支援AddFeedback()方法",而Person類別可以提供一些AddFeedback()方法的預設程式碼-你對之的理解可以是"如果你不選擇支援AddFeedback(),那麼你應該提供一種預設實作。如果你能夠簡單地勾勒出你的類別所要實現的一組期望的功能,那麼你也可以使用一個介面。
三、應用多態性設計
我們將繼續使用Person基類別的例子,現在讓我們分析一個非多態性的實作。下列範例中使用了不同類型的Person物件--這是一種非常不理想的程式設計方式。注意,實際的Person類別被省略。目前為止,我們僅關心程式碼呼叫的問題。
<?php
$name = $_SESSION['name'];
$myPerson = Person::GetPerson($name);
switch (get_class($myPerson)){
case 'David' :
$myPerson->AddFeedback('Great Article!','Some Reader', date('Ym-d'));
break;
case 'Charles':
$myPerson->feedback[] = array('Some Reader', 'Great Editing!');
break;
case 'Alejandro' :
$myPerson->Feedback->Append('Awesome Javascript!');
break;
default :
$myPerson->AddFeedback('Yay!');
}
?>
這個範例展示了行為不同的對象,還有一個switch語句用來區分不同的Person類別對象,從而執行其各自對應的正確操作。注意,這裡針對不同條件的回饋註釋是不同的。在實際應用程式開發中可能不會出現這種情況;我僅為了簡單地說明類別實作中存在的差異。
下面的一個範例使用了多態性。
<?php
$name = $_SESSION['name'];
$myPerson = Person::GetPerson($name);
$myPerson->AddFeedback('Great Article!', 'SomeReader', date('Ym-d'));
?>
注意,這裡沒有switch語句,而最重要的是,缺乏有關Person::GetPerson()會傳回哪種類型的物件。而另一個Person::AddFeedback()是一個多型方法。行為完全是由具體類別進行封裝的。請記住,在此無論我們使用的是David,Charles還是Alejandro,呼叫程式碼從來不必了解特定類別的功能,而只知道基底類別就可以了。
儘管我的範例並不完美,但是,從呼叫程式碼的角度,它已經展示了多態性的基本用法。現在我們需要分析這些類別的內部實作。從一個基底類別進行派生的一個最偉大的地方在於,該派生類別能夠訪問父類別的行為,這種情況常常是缺省的實現,但是也可能出現在類別繼承鏈中用於創建更為複雜的行為。下面是這種情況的一個簡單展示。
<?php
class Person{
function AddFeedback($comment, $sender, $date){
//把回饋加到資料庫}
}
class David extends Person{
function AddFeedback($comment, $sender){
parent::AddFeedback($comment, $sender,
date('Ym-d'));
}
}
?>
在此,David類別中的AddFeedback方法實作中首先呼叫了Person::AddFeedback方法。你可能注意到,它模仿了C++,Java或C#中的方法重載。請記住,這只是一個簡單化的範例,而你寫的實際程式碼完全依賴你的實際工程。
四、PHP 5中的遲綁定
依我的看法,遲綁定正是使得Java和C#如此引人注目的重要原因。它們允許基類方法用"this"或$this來呼叫方法(即使它們不存在於基類中或調用一個基類中的方法,它有可能為繼承類中的另一個版本所代替)。你可以認為如下的實作在PHP中是允許的:
<?php
class Person{
function AddFeedback($messageArray) {
$this->ParseFeedback($messageArray);
//寫向資料庫}
}
class David extends Person{
function ParseFeedback($messageArray){
// 進行一些分析}
}
?>
記住,在Person類別中並沒有ParseFeedback。現在,假定你擁有這一部分實作程式碼(為了本例說明問題起見),那麼這會導致$myPerson成為一個David物件:
<?php
$myPerson = Person::GetPerson($name);
$myPerson->AddFeedback($messageArray);
?>
出現分析錯誤!大致錯誤訊息為,方法ParseFeedback並不存在或一些類似的訊息。關於PHP 5的遲綁定我們就討論這些!下面我們再歸納一下遲綁定的概念。
遲綁定意味著,方法呼叫在最後時刻才綁定到目標物件。這意味著,當該方法被運行時刻呼叫時,那些物件已經有了一種具體類型。在我們上面的範例中,你呼叫了David::AddFeedback(),而既然David::AddFeedback()中的$this引用一個David對象,那麼你可以邏輯地假定ParseFeedback()方法是存在的--但事實上它並不存在,因為AddFeedback()是在Person中定義的,並且從Person類別呼叫ParseFeedback()。
不幸的是,沒有簡單的方法來消除PHP 5的這種行為。這意味著,當你想創造一個靈活的多態類別層次時你可能有點無能為力。
我必須指出,我選擇PHP 5作為本文的表達語言只是因為:這種語言並沒有實現物件概念的完美抽象!因為PHP 5仍處於其測試版本運行期,所以這是可以諒解的。另外,既然語言中加入了抽象類別和接口,遲綁定也應該被實作。
五、小結
至此,你應該基本了解什麼是多態性以及為什麼PHP 5在實現多態性方面並不完美。一般說來,你應該知道如何用一個多態性物件模型來封裝有條件的行為。當然,這會提高你的物件的靈活性,並且意味著更少的程式碼實作。另外,透過封裝滿足一定條件的行為(具體要依賴物件的狀態),你也提高了程式碼的清晰程度。