1. Trait 是什麼
Trait [treɪt] 翻譯過來是 "特性"、"特點" 、"特質",是一種在 PHP 中複用代碼的形式。
我們看下官方的解釋:
Trait 是為類似 PHP 的單繼承語言而準備的一種代碼複用機制。Trait 為了減少單繼承語言的限制,使開發人員能夠自由地在不同層次結構内獨立的類中複用 method。
Trait 和 Class 組合的語義定義了一種減少複雜性的方式,避免傳統多繼承和 Mixin 類相關典型問題。
上述說明可以提取出幾個關鍵詞:代碼複用、單繼承、減少複雜性。
說到單繼承,不得不提到另外一個特性:多态。多态和繼承是軟體開發中常用的代碼複用方式,但是繼承的方式雖然也能解決問題,但其思路違背了面向對象的原則,顯得很粗暴;多态方式也可行,但不符合軟體開發中的 DRY ( Don't repeat yourself ) 原則,增加了維護成本。
此時此刻,Trait 以一種全新的繼承方式出現了,它既解決了前文叙述的兩種繼承方式的弊端,也相對優雅的實作了代碼的複用。
簡單說一下 Trait 在底層的運作原理:PHP 解釋器在編譯代碼時會把 Trait 部分代碼複制粘貼到類的定義體中,但是不會處理這個操作引入的不相容問題。(是不是很厲害)
2. 定義和使用 Trait
接下來我還會沿用第二節課中的例子,但這次不是買手機,而是自己做手機。假設我們是一家比較特殊的手機廠商,為什麼特殊呢,因為我們什麼品牌的手機都做(就當是山寨工廠吧),廢話不多說,我來說一下制作的規則:
假設我們掌握了三種手機的制作工藝,他們分别是:小米 Note3、三星 Galaxy S8 和 iPhone X,他們各有各的特點(相對于其他兩種品牌獨一無二)還有相同的特點(面部識别),為友善大家了解,我畫了一張圖(看不清圖檔的同學請點選圖檔檢視大圖):
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0gTMx81dsQWZ4lmZf1GLlpXazVmcvwFciV2dsQXYtJ3bm9CX9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cGcq5SOwkTO1YzNlVWN3QjZxEWZyYzX5UTOwUTMzAzLchDMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.jpg)
簡單解釋一下:衆所周知,這三部手機都有面部識别的功能(上圖左側部分),這屬于相同點,其實他們的相同點不止這些(比如都可以打電話、發短信等),而了解他們的同學也都知道,其中任何一部手機都有相對于其他手機沒有的功能(上圖右側部分)。
下面我将用代碼的形式講解,當把這些手機抽象為 PHP 類的概念時,如何利用 PHP Trait 更好更優雅的實作這個圖中的功能。
首先,我需要把『面部識别』這三個手機都有的功能抽象為一個 Trait,請看代碼示例:
// 小米 Note3 三星 S8 iPhone X
// 共同擁有的面部識别功能
trait Faceable {
protected $face_id = 0;
// 就當我是擷取面部資訊的功能
public function getFace()
{
//...
return $this->face_id;
}
// 就當我是設定面部資訊的功能
public function setFace(string $face_id)
{
//...
$this->face_id = $face_id;
}
}
定義一個 Trait 是不是很簡單,除特殊關鍵字以外,内部構造其實和 PHP 普通的類沒啥差別。這裡需要注意的是,Trait 的命名規範最好是以able 結尾,這樣友善我們自己識别和了解。其次,建議每個檔案隻定義一種性狀,這是良好的實踐。
接下來就是定義這三部手機的類了,需要注意的是,我在實作這三部手機中特有的功能外,同時引用了我剛才定義的面部識别 Trait。
// 小米 Note3
class MiNote3 {
// 引入面部識别 Trait
use Faceable;
// 獨有的 MIUI
protected $miui;
// 初始化MIUI
public function __construct($miui)
{
$this->miui = $miui;
$this->bootUI();
}
private function bootUI()
{
return $this->miui;
}
//...
}
注:小米的 MIUI 可以說是安卓陣營的佼佼者,用來當做其獨特的特性不為過。
// 三星 GalaxyS8
class SamsangS8 {
// 引入面部識别 Trait
use Faceable;
// 獨有的 Bixby
protected $bixby;
public function __construct()
{
$this->sayHello();
}
private function sayHello()
{
echo "Hi I am Bixby!";
}
//...
}
注:Bixby 是三星近期釋出的語音助手,雖然還在内測中,我有幸提前體驗了一把,感覺各個方面都碾壓蘋果的 Siri 和其他語音助手。是以把這個 Bixby 的功能作為一個三星S8獨有的特性,抽象出來一個類。
// iPhoneX
class iPhoneX {
// 引入面部識别 Trait
use Faceable;
// 獨有的 TrueDepth
protected $true_depth;
public function __construct()
{
$this->openCamera();
}
private function openCamera()
{
return $this->true_depth;
}
//...
}
注:iPhoneX 最好玩的特點莫過于前置的深感攝像頭了,可以把表情動作賦給 emoji,這個功能就可以作為這個手機獨有的特色抽象出來一個類。
通過以上示例,我們就可以把『面部識别』這三部手機都擁有的特性很輕松的組裝到這三部手機中,使他們都具有完整性。用代碼來說呢,就是每個類都使用了一個公共的 PHP Trait,在不改變自身的前提下,擁有了額外的屬性。
3. Trait 的好處
有的人看完上述示例可能會有這樣的疑問:與其像代碼裡這麼寫,我不如建立一個名為 Mobile 的類,在這個類裡完成面部識别的功能,然後讓三個手機的類直接繼承 Mobile 類不就行了嗎?比如:
// 手機公共類
class Mobile {
protected $message;
//...
public function __construct($message)
{
$this->message = $message;
}
public function getMessage()
{
return $this->message;
}
// 實作了開關機 鬧鐘 音樂播放等功能...
# code...
}
然後這樣引用
// 小米Note3
class MiNote3 extends Mobile {
// ...
}
// GalaxyS8
class SamsangS8 extends Mobile {
// ...
}
答案是可以的,但是你有沒有想過,除了『面部識别』,是不是還有其他的特性我沒有列出來?比如三星和蘋果都有的超大分辨率,小米沒有;小米和三星都是安卓系統,而蘋果不是。這樣的關系,你如果都寫在一個類裡,由于 PHP 隻有單繼承的原因,會使你的 Mobile 類顯得異常臃腫,難以解讀。
// 小米Note3
class MiNote3 {
use Faceable,Androidable;
// ...
}
// GalaxyS8
class SamsangS8 {
use Faceable,Androidable,HDisplayable;
// ...
}
// iPhoneX
class iPhoneX {
use Faceable,HDisplayable;
// ...
}