Traits
我的許多PHP開發者朋友都不太了解traits,這是PHP 5.4.0中引入的新概念。 traits看起來像介面但是用起來像類,那究竟是什麼呢?兩者都不是。
一個trait擁有部分實現(譬如常量、屬性和方法),可以被植入到一個或多個實際的PHP類中。 trait有兩個職責:表示一個類別可以做什麼(類似介面);提供一個模組化的實作(類似類別)。
在其他的語言中你也許已經對traits有了一定的了解。譬如Ruby的modules及mixins功能就和PHP的traits很類似。
我們為什麼要使用traits
PHP語言使用的是經典的繼承模型。這意味著你從一個提供了基本實作的通用的根類別開始。從根類創造出更具體的類,它們直接繼承了父類的各種實作。這叫做繼承層次,許多程式語言使用的都是這種公用的模式。
為了方便理解,假設你穿越回高中學習生物。還記得你學習的生物的界門綱目科屬種嗎?總共有六大界,界派生出門、門派生出綱、綱派生出目、目派生出科、科派生出屬、屬後面是種。在物種的層級上的每次向下的派生都代表著具體的特性。
經典的繼承模型大多數情況下都能工作的很好。但是,如果有兩個毫無關聯的類別需要實現類似的行為該如何解決?例如,一個PHP類叫RetailStore,而另一個PHP類叫Car,它們兩個可以是說是完全風馬牛不相及的兩個類,在繼承關係上根本無法共享一個公用的父類。然而兩個類別都需要使用地理位置中的經度和緯度來顯示地圖座標。
我們創造traits就是來解決這個問題的。它們可以將部分實現注入到不相干的類別中。 traits也同樣利於程式碼的重用。
遇到這個問題,我的第一個解決方案(也是最糟糕的)是創造一個公共的父類Geocodable用來給RetailStore和Car這兩個類別來繼承。這個解決方案實在是太糟糕了,因為強行讓兩個毫不相干的類別取共享一個公共的祖先,在它們各自的繼承層級上都顯得格外彆扭。
我的第二個解決方案(稍微好點)是創造一個Geocodable介面來定義實作地理位置需要哪些方法。 RetialStore和Car兩個類別都可以實作這個Geocodable介面。讓每個類別都能保留各自自然的繼承關係確實是個很好的解決方案。但是我們還是需要在每個類別裡都重複的去實作介面裡的定義,這可不是一個DRY的方案。
DRY是Do not repeat yourself的縮寫。作為一個好的程式設計習慣,我們永遠不要在多個地方重複同樣的程式碼。不能出現因為改了一處程式碼,而被動的還要去修改其他地方同樣的程式碼的狀況。
我的第三個方案(最佳方案)是建構一個Geocodable的trait,在裡面定義並實現相關的方法。我可以在不打亂類別的繼承層級的情況下把Geocodable的trait加到RetailStore類別和Car類別中。
如何構造一個trait
下面展示的是如何定義一個PHP的trait:
<?php trait MyTrait { // 此处是trait的具体实现 }
讓我們回到我們的Geocodable範例來更好的示範一下trait的使用。我們都知道RetailStore類別和Car類別需要支援地理位置的定位功能,而我們也都能認同繼承和介面並不是最佳方案。取而代之,我們建構一個Geocodable trait來傳回一個可以在地圖上標記的經度和緯度座標。範例2-12中是我們完整的Geocodable trait。
例子 2-12 Geocodable trait的定義
<span style="font-size:14px;"><?php trait Geocodable { /** @var string */ protected $address; /** @var \Geocoder\Geocoder */ protected $geocoder; /** @var \Geocoder\Result\Geocoded */ protected $geocoderResult; public function setGeocoder(\Geocoder\GeocoderInterface $geocoder) { $this->geocoder = $geocoder; } public function setAddress($address) { $this->address = $address; } public function getLatitude() { if (isset($this->geocoderResult) === false) { $this->geocodeAddress(); } return $this->geocoderResult->getLatitude(); } public function getLongitude() { if (isset($this->geocoderResult) === false) { $this->geocodeAddress(); } return $this->geocoderResult->getLongitude(); } protected function geocodeAddress() { $this->geocoderResult = $this->geocoder->geocode($this->address); return true; } }</span>
我們的Geocodable trait定義了三個類別的屬性:
未完待續…
以上就介紹了[Modern PHP] 第二章 新功能之三 Traits,包括了方面的內容,希望對PHP教程有興趣的朋友有所幫助。