请简述一下 PHP Trait
的概念和用法
作为一个老程序员,在面试的时候被面试官用这个问题嘲讽了好一阵... 由于一直使用老的框架,再加上懒惰,导致我根本没法完整的回答这个问题,今天就带领各位一起学习(复习)这个概念,希望各位能有所收获。
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,他们各有各的特点(相对于其他两种品牌独一无二)还有相同的特点(面部识别),为方便大家理解,我画了一张图(看不清图片的同学请点击图片查看大图):
简单解释一下:众所周知,这三部手机都有面部识别的功能(上图左侧部分),这属于相同点,其实他们的相同点不止这些(比如都可以打电话、发短信等),而了解他们的同学也都知道,其中任何一部手机都有相对于其他手机没有的功能(上图右侧部分)。
下面我将用代码的形式讲解,当把这些手机抽象为 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 类显得异常臃肿,难以解读。
但是你使用了 Trait
之后,我们只需要再提取出『安卓系统』和『高分辨率』这两个特性,就可以很方便的在这三个类里随意组合,而且还能保证你的代码非常清晰。
// 小米Note3
class MiNote3 {
use Faceable,Androidable;
// ...
}
// GalaxyS8
class SamsangS8 {
use Faceable,Androidable,HDisplayable;
// ...
}
// iPhoneX
class iPhoneX {
use Faceable,HDisplayable;
// ...
}
这样看起来是不是清晰很多呢?他不仅降低了代码的耦合性,还提升了代码的可读性。依我看来,他不光是某种特性的集合,更像是将某个功能细化了的代码块。
但还请大家记住,所有思路的设计没有对错之分,只有好坏之分。不要为了两段相同的能够写在一起而将完整的属性无脑拆拆拆,优雅设计的背后都有深思熟虑的设计过程。