什麼是多態性?
Polymorphism(多態性)是一個很長的單詞,但是它表示的是一個非常簡單的概念。
多態性描述了在物件導向編程模式中類有不同的功能,而共用一個通用的介面。
多態性的優點是,並不需要知道它使用的是哪一個類,因為他們都用同樣的方式與不同的類的代碼工作。
可將多態性類比成現實世界的一個按鈕。大家都知道如何使用一個按鈕:您只需給它施加壓力。一個按鈕“確實是這樣”,然而,取決於它和什麼串連和使用它的上下文 - 但結果並不影響它是如何使用。如果你的老闆告訴你按下一個按鈕,您已經有執行任務所需的所有資訊。
在編程的世界中,多態性是用來使應用程式更加模組化和可擴充的。相比於淩亂的條件陳述式描述的行動不同的課程,您可以建立根據您的需求選擇的互換對象。這是多態性的基本目標。
Interfaces介面
介面是與類(class)類似的,除了它不能包含代碼。介面可以定義方法名稱和參數,但不是方法的內容。實現介面的任何類都必須實現介面中定義的所有方法。一個類可以實現多個介面。
使用的“interface”關鍵字聲明一個介面:
複製代碼 代碼如下:interface MyInterface {
// methods
}
被附加到一個類,使用“implements”關鍵字(多個介面,可以通過用逗號分開): 複製代碼 代碼如下:class MyClass implements MyInterface {
// methods
}
在介面中可以像在類中一樣定義方法,除了沒有方法體(在大括弧中的部分)以外。 複製代碼 代碼如下:interface MyInterface {
public function doThis();
public function doThat();
public function setName($name);
}
在這裡定義的所有方法都必須如介面中所描述地那樣被包含在任何實現它的類中。(讀下面的代碼注釋) 複製代碼 代碼如下://合法的 VALID
class MyClass implements MyInterface {
protected $name;
public function doThis() {
// code that does this
}
public function doThat() {
// code that does that
}
public function setName($name) {
$this->name = $name;
}
}
// 非法的INVALID
class MyClass implements MyInterface {
// missing doThis()!
private function doThat() {
// this should be public!
}
public function setName() {
// missing the name argument!
}
}
抽象類別Abstract Class
抽象類別是介面和類的混合。它可以像介面一樣定義方法。繼承自抽象類別的類必須實現抽象類別中定義的所有抽象方法。
抽象類別的定義方式與類一樣,不過是在前面附加了一個abstract 關鍵字。 複製代碼 代碼如下:abstract class MyAbstract {
// methods
}
並且 是用 ‘extends‘ 關鍵字附加到類: 複製代碼 代碼如下:class MyClass extends MyAbstract {
// class methods
}
就像在普通類中一樣,普通的方法以及任何抽象方法(使用關鍵字“abstract”)可以在抽象類別中定義。抽象方法的行為就像在介面中定義的的方法,而且繼承它的擴充類中必須實現完全一樣的定義。 複製代碼 代碼如下:abstract class MyAbstract {
public $name;
public function doThis() {
// do this
}
abstract public function doThat();
abstract public function setName($name);
}
我們假設你有一個文章Article類負責管理你網站上的文章。它包含關於文章的資訊,包括:title, author, date, and category.
就像下面這樣: 複製代碼 代碼如下:class poly_base_Article {
public $title;
public $author;
public $date;
public $category;
public function __construct($title, $author, $date, $category = 0) {
$this->title = $title;
$this->author = $author;
$this->date = $date;
$this->category = $category;
}
}
注意:在這個教程中的樣本類使用了“package_component_Class”的命名規範,這是一個用來將類名分隔到虛擬命名空間來避免命名衝突的通用方法。
現在你想添加一個方法來輸出各種不同格式的資訊,如XML和JSON。你也許會打算像下面這樣做: 複製代碼 代碼如下:class poly_base_Article {
//...
public function write($type) {
$ret = '';
switch($type) {
case 'XML':
$ret = '';
$ret .= '';
$ret .= '' . $obj->author . '';
$ret .= '' . $obj->date . '';
$ret .= '' . $obj->category . '';
$ret .= '';
break;
case 'JSON':
$array = array('article' => $obj);
$ret = json_encode($array);
break;
}
return $ret;
}
}
這種解決方案是醜陋的,但是它是可靠的——至少在現在。問下你自己將來會發生什麼,當我們需要添加更多格式的時候?你可以繼續編輯這個類,然後添加越來越多的case ,
但是現在你只是在稀釋(FIX ME: diluting) 你的類。
OOP的一個重要原則是一個類應該做的一件事情,而應該把它做好。
考慮到這一點,條件陳述式應該是一個紅色的標誌,表明你的類是試圖做太多不同的東西。這是多態性的用武之地。
在我們的例子中,有兩個任務明確的提出:管理文章和格式化其資料。在本教程中,我們將重構我們的格式化代碼到一個新的類,然後我們會發現使用多態性是多麼容易。
Step 2: 定義你的介面 Define Your Interface
第一件事就是我們應該定義介面,努力想好怎麼定義你的介面是件重要的事情,因為對它的任何改變都將需要改動調用它的代碼。
在我們這個例子中,我們將使用一個簡單的介面來定義一個方法: 複製代碼 代碼如下:interface poly_writer_Writer {
public function write(poly_base_Article $obj);
}
它就是那樣簡單,我們已經定義了一個公用方法write() ,它接受一個文章對象作為參數。任何實現Writer介面的類都將會確保有這個方法。
小提示:如果你想嚴格限制傳遞給你的方法和函數的參數類型,你可以使用類型提示,就像我們已經在write()方法中做的一樣,它只能接受poly_base_Article 物件類型的數
據。不幸的是,在目前版本的PHP中,傳回型別提示是不被支援的,所以,你要小心傳回值的類型了。
Step 3: 建立實作類別 Create Your Implementation
定義介面後,該是時候來建立類來真正幹活的啦。在我們的例子中,我們需要輸出兩種格式。這樣,我們就要兩個Writer 類:XMLWriter 和 JSONWriter 。從傳遞過來的
Article 對象提取資料然後格式化這些資訊完全取決於這些類了。
下面是 XMLWriter 類的一個樣本: 複製代碼 代碼如下:class poly_writer_XMLWriter implements poly_writer_Writer {
public function write(poly_base_Article $obj) {
$ret = '';
$ret .= '';
$ret .= '' . $obj->author . '';
$ret .= '' . $obj->date . '';
$ret .= '' . $obj->category . '';
$ret .= '';
return $ret;
}
}
就如你可以從類定義中看到的一樣,我們使用implements關鍵字來實現我們的介面。write() 方法包含格式化為XML的功能。
現在我們來看下JSONWriter 類: 複製代碼 代碼如下:class poly_writer_JSONWriter implements poly_writer_Writer {
public function write(poly_base_Article $obj) {
$array = array('article' => $obj);
return json_encode($array);
}
}
現在,我們的代碼中的特定每種格式都包含在單獨的類。每個類有全權負責處理特定的格式,而不是其他。您的應用程式中沒有其他部分需要關心這些是如何工作的才能使用它,
感謝我們的介面。
Step 4: 使用你的介面Use Your Implementation
在我們的新類定義後,該是時候來重溫一下我們的Article類了,所有原write() 方法中的代碼已經被分離出來,進入到我們的新類中了。
我們的所有方法現在需要做的就是使用這些新的類,像這樣: 複製代碼 代碼如下:class poly_base_Article {
//...
public function write(poly_writer_Writer $writer) {
return $writer->write($this);
}
}
擷取一個 Writer對象 Obtaining A Writer
你也許會疑惑你該從哪裡擷取一個 Writer對象開始,因為你需要傳遞一個 Writer對象到這個方法。
這完全取決於你,並且,有很多策略。如,你可能會使用工廠類來擷取請求資料然後建立一個對象: 複製代碼 代碼如下:class poly_base_Factory {
public static function getWriter() {
// grab request variable
$format = $_REQUEST['format'];
// construct our class name and check its existence
$class = 'poly_writer_' . $format . 'Writer';
if(class_exists($class)) {
// return a new Writer object
return new $class();
}
// otherwise we fail
throw new Exception('Unsupported format');
}
}
就像我說的,根據你的需求,有好多其它策略可用。在這個例子中,通過一個請求變數選擇哪種格式是要使用的。它基於request請求變數來構造一個類名,檢測它是否存在,
然後返回一個新的Writer對象。如果沒有那個名字的類存在,拋出一個異常,讓用戶端代碼決定接下來要幹什麼。
Step 5: 把它們放一起 Put It All Together
當所有東東都到位了,下面是我們的用戶端代碼如何放在一起: 複製代碼 代碼如下:$article = new poly_base_Article('Polymorphism', 'Steve', time(), 0);
try {
$writer = poly_base_Factory::getWriter();
}
catch (Exception $e) {
$writer = new poly_writer_XMLWriter();
}
echo $article->write($writer);
首先,我們建立了一個樣本 Article 對象來配合工作。然後,我們試圖從工廠Factory擷取一個Factory對象,如果異常發生的話復原到預設(XMLWriter) 。
最後,我們傳遞Writer對象給我們的Article的 write() 方法,輸出結果。
結論 Conclusion
在本教程中,我提供了一個多態性的簡介而且解釋了PHP中的介面。我希望你意識到,我只向您展示一個潛在的使用多態性的案例。
多態性是以一個優雅的方式來避免您的OOP代碼中醜陋的條件陳述式。它遵循的原則是使您的組件分離,而且它是許多設計模式的組成部分。如果您有任何問題,不要猶豫,在評論中提問!
譯自:http://net.tutsplus.com/tutorials/php/understanding-and-applying-polymorphism-in-php/
原文發表在:http://ihacklog.com/?p=4703