我最近一直在努力研究PHP,我發現本身陷入了特質。 我理解水平代碼重用的概念,而不是必須從抽象類繼承。 我不明白的是:使用特徵與接口之間的關鍵區別是什麼? php
我已經嘗試過尋找一個體面的博客文章或解釋什麼時候使用其中一個的文章,但到目前爲止我發現的例子看起來很是類似。 程序員
一個常常用來描述Traits的比喻是Traits是與實現的接口。 數組
在大多數狀況下,這是一種很好的思考方式,但二者之間存在一些細微差異。 函數
首先, instanceof
運算符不能使用traits(即,trait不是真實對象)因此你不能讓咱們看看一個類是否具備某種特徵(或者看看兩個不相關的類是否共享一個特徵)特徵)。 這就是它們做爲水平代碼重用的構造的意思。 oop
PHP中如今有一些函數可讓你得到一個類使用的全部特徵的列表,但特徵繼承意味着你須要進行遞歸檢查以可靠地檢查一個類在某個時刻是否具備特定的特徵(這裏有一個例子) PHP doco頁面上的代碼)。 可是,它確定不像instanceof那麼簡單和乾淨,恕我直言,這是一個可使PHP變得更好的功能。 spa
此外,抽象類仍然是類,所以它們不能解決與多繼承相關的代碼重用問題。 請記住,您只能擴展一個類(實際或抽象),但實現多個接口。 .net
我發現traits和接口很是適合用手建立僞多重繼承。 例如: code
class SlidingDoor extends Door implements IKeyed { use KeyedTrait; [...] // Generally not a lot else goes here since it's all in the trait }
這樣作意味着您可使用instanceof來肯定特定的Door對象是否爲Keyed,您知道您將得到一組一致的方法等,而且全部代碼都位於使用KeyedTrait的全部類中的一個位置。 對象
基本上,您能夠將特徵視爲代碼的自動「複製粘貼」。 繼承
使用特徵是危險的,由於在執行以前沒有必要知道它的做用。
可是,因爲缺少繼承等限制,特徵更加靈活。
特徵能夠用於注入將某些東西檢查到類中的方法,例如,存在另外一個方法或屬性。 一篇很好的文章(但用法語,對不起) 。
對於可以得到它的法語讀者,GNU / Linux Magazine HS 54有一篇關於這個主題的文章。
一個trait
是基本上是PHP的一個實施的mixin
,而其實是一組能夠經過添加的添加到任何類的擴展方法trait
。 而後,這些方法成爲該類實現的一部分,但不使用繼承 。
從PHP手冊 (強調個人):
特徵是一種在單繼承語言(如PHP)中重用代碼的機制。 ......它是對傳統繼承的補充,能夠實現行爲的橫向組合; 也就是說,類成員的應用程序不須要繼承。
一個例子:
trait myTrait { function foo() { return "Foo!"; } function bar() { return "Bar!"; } }
經過定義上述特徵,我如今能夠執行如下操做:
class MyClass extends SomeBaseClass { use myTrait; // Inclusion of the trait myTrait }
此時,當我建立一個MyClass
類的實例時,它有兩個方法,名爲foo()
和bar()
- 來自myTrait
。 而且 - 注意trait
定義的方法已經有一個方法體 - Interface
定義的方法不能。
另外 - 與許多其餘語言同樣,PHP使用單個繼承模型 - 這意味着類能夠從多個接口派生,但不能從多個類派生。 可是,PHP類能夠有多個trait
包含 - 這容許程序員包含可重用的部分 - 若是包含多個基類的話。
有幾點須要注意:
----------------------------------------------- | Interface | Base Class | Trait | =============================================== > 1 per class | Yes | No | Yes | --------------------------------------------------------------------- Define Method Body | No | Yes | Yes | --------------------------------------------------------------------- Polymorphism | Yes | Yes | No | ---------------------------------------------------------------------
多態性:
在前面的示例中, MyClass
擴展了 SomeBaseClass
, MyClass
是 SomeBaseClass
一個實例。 換句話說,諸如SomeBaseClass[] bases
的數組能夠包含MyClass
實例。 相似地,若是MyClass
擴展了IBaseInterface
,則IBaseInterface[] bases
能夠包含MyClass
實例。 沒有這樣的多態結構可用於trait
- 由於trait
基本上只是代碼,爲了程序員的方便而複製到使用它的每一個類中。
優先級:
如手冊中所述:
來自基類的繼承成員被特徵插入的成員覆蓋。 優先順序是來自當前類的成員重寫Trait方法,這些方法返回覆蓋繼承的方法。
因此 - 考慮如下場景:
class BaseClass { function SomeMethod() { /* Do stuff here */ } } interface IBase { function SomeMethod(); } trait myTrait { function SomeMethod() { /* Do different stuff here */ } } class MyClass extends BaseClass implements IBase { use myTrait; function SomeMethod() { /* Do a third thing */ } }
在上面建立MyClass的實例時,會發生如下狀況:
Interface
IBase
須要提供名爲SomeMethod()
的無參數函數。 BaseClass
提供了此方法的實現 - 知足了需求。 trait
myTrait
提供了一個名爲SomeMethod()
無參數函數, 它優先於BaseClass
-version MyClass
class
提供了本身的SomeMethod()
版本 - 它優先於trait
-version。 結論
Interface
不能提供方法體的默認實現,而trait
能夠。 Interface
是一個多態的 , 繼承的構造 - 而trait
則不是。 Interface
能夠在同一個類中使用,所以可使用多個trait
。 特徵僅用於代碼重用 。
接口只提供要在類中定義的函數的簽名 ,能夠根據程序員的判斷使用它。 從而爲咱們提供了一組課程的原型 。
供參考 - http://www.php.net/manual/en/language.oop5.traits.php
主要區別在於,使用接口,您必須在實現所述接口的每一個類中定義每一個方法的實際實現,所以您可讓許多類實現相同的接口但具備不一樣的行爲,而traits只是注入的代碼塊一類; 另外一個重要的區別是特徵方法只能是類方法或靜態方法,不像接口方法也能夠(一般是)實例方法。