為什麼 PHP 會引入 Trait ? 我們先來看看軟體開發中的兩種常用代碼複用模式,
繼承和
組合。
- 繼承:強調 父類與子類 的關系,即子類是父類的一個特殊類型;
- 組合:強調 整體與局部 的關系,側重的一種需要的關系;
軟體開發中有一條原則,叫做
組合優于繼承。這是因為
從耦合度來看,繼承要高于組合。繼承關系中,子類與父類保持着高度的依賴關系,加上 PHP 不支援多繼承,為了避免重寫編寫代碼,很多功能都被統一封裝到父類中。這樣做有兩個壞處:一是随着繼承的層數和子類的增加,代碼複雜度不斷增加,大量的方法都将面臨着重寫;二是這些功能對于一些子類來說可能是不必要的,破壞了代碼的封裝性。
Trait 的提出彌補了 PHP 對組合支援的不足,一個 Trait 就相當于一個子產品,不同的 Trait 以組合的方式注入到類中。我們以 Laravel 的控制器為例,來介紹下繼承群組合是如何在具體的場景中使用的。
首先,
底層的代碼應當多使用組合。Laravel 的底層控制器隻繼承了一個簡單的控制器
IlluminateRoutingController
,結構相對穩定。同時,控制器使用了不同的
Trait
來組織代碼,避免了對象的臃腫,極大程度的保持了架構的靈活性。
<?php
namespace AppHttpControllers;
use IlluminateFoundationAuthAccessAuthorizesRequests;
use IlluminateFoundationBusDispatchesJobs;
use IlluminateFoundationValidationValidatesRequests;
use IlluminateRoutingController as BaseController;
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
}
而
具體的業務邏輯或頂層代碼應當多使用繼承,這樣能夠大大提高的開發效率
<?php
use AppHttpControllersController;
class UserController extends Controller
{
}
以上就是繼承群組合的簡單介紹。接下來看看
Trait
的具體使用。
推薦文章目錄:八重櫻:PHP進階程式員必看知識點:目錄大全(不定期更新)zhuanlan.zhihu.com
使用
規範
Symfony 編碼規範建議在每個
Trait
之後添加
Trait
關鍵字。
namespace SymfonyContractsTranslation;
trait TranslatorTrait {
}
PSR-12 規範建議在每個
Trait
使用一個
use
語句來聲明,同時
Trait
與類的其他成員需要保持一行空行。
class ClassName
{
use FirstTrait;
use SecondTrait;
use ThirdTrait;
public $a;
}
成員
Trait
中可包含屬性、方法 與 抽象方法,這三者的結合既可以複用代碼,也可以對代碼的使用作出一些約定,例如 Laravel 中的自動維護
slug
字段
<?php
namespace AppTraits;
use IlluminateSupportStr;
trait HasSlug
{
public static function bootSluggable()
{
static::saving(function ($model) {
$model->slug = Str::slug(
$model->getAttribute( $model->sluggable() )
);
});
}
/**
* Slug 字段
*
* @return string
*/
abstract public function sluggable(): string;
}
Trait
中也可以包括靜态屬性和靜态方法,以下是一個單例模式的簡單封裝。
trait Singleton
{
private static $instance;
public static function getInstance() {
if (!(self::$instance instanceof self)) {
self::$instance = new self;
}
return self::$instance;
}
}
每個
Trait
中可以包含其他
Trait
,進一步提高了代碼的靈活性
<?php
trait Hello
{
function sayHello() {
echo "Hello";
}
}
trait World
{
function sayWorld() {
echo "World";
}
}
class MyWorld
{
use Hello;
use World;
}
Trait 與類的沖突處理
當存在同名方法時,目前類的方法會覆寫
Trait
中的方法,而
Trait
中的方法會覆寫父類的方法。
<?php
// 父類,優先級最低
class Base {
public function sayHello() {
echo 'Hello ';
}
}
// Trait 優先級大于父類
trait SayWorld {
public function sayHello() {
parent::sayHello();
echo 'World!';
}
}
// 目前類,優先級最高
class MyHelloWorld extends Base {
use SayWorld;
}
$o = new MyHelloWorld();
$o->sayHello(); // Hello, World
當存在同名屬性時,類的屬性
必須與
Trait
的屬性相容(相同的通路性、相同的初始值),否則會報
緻命錯誤。
<?php
trait Foo {
public $same = true;
public $different = false;
}
class Bar {
use Foo;
public $same = true; // 合法
public $different = true; // fatal error
}
Trait 與 Trait 的沖突處理
當一個類包含多個
Trait
時,不同
Trait
之間可能會存在屬性和方法的沖突。
當存在相同屬性時,屬性
必須相容,跟
Trait
與類的沖突處理類似
Trait A {
public $a = 'foo';
}
Trait B {
public $a = 'foo';
}
class Foo
{
use A;
use B;
}
當存在方法沖突時,需要使用
insteadof
來手動處理沖突,否則會報緻命錯誤
<?php
Trait A {
public function foo()
{
return "A foo";
}
}
Trait B {
public function foo()
{
return "B foo";
}
}
class Bar {
use A, B {
B::foo insteadof A; // 用 B 的 foo 方法來代替 A
}
}
$bar = new Bar();
echo $bar->foo(); // B foo
這時候如果想要保留 A 的
foo
方法,可以用
as
定義别名來進行調用。注意,起别名僅僅代表可以用别名來調用該方法,仍然需要用
insteadof
處理沖突
class Bar {
use A, B {
B::foo insteadof A; // 用 B 的 foo 方法來代替 A
A::foo as aFoo; // A 的 foo 方法用 aFoo 來調用
}
}
$bar = new Bar();
echo $bar->aFoo(); // A foo
as
關鍵字還可以用來更改方法法的通路控制
<?php
Trait A {
public function foo()
{
return "A foo";
}
}
class Bar {
use A {
A::foo as private;
}
}
$bar = new Bar();
echo $bar->foo(); // Fatal error: Uncaught Error: Call to private method Bar::foo()
這兩者可以結合起來用,這時候原有方法的通路控制就不會受到影響
<?php
Trait A {
public function foo()
{
return "A foo";
}
}
class Bar {
use A {
A::foo as private aFoo;
}
}
$bar = new Bar();
echo $bar->foo(); // A foo,照常調用
echo $bar->aFoo(); // 被設定成私有方法,是以報錯。Fatal error: Uncaught Error: Call to private method Bar::foo()
很多人在剛接觸這個行業的時候或者是在遇到瓶頸期的時候,總會遇到一些問題,比如學了一段時間感覺沒有方向感,不知道該從那裡入手去學習,對此我整理了一些資料,需要的可以免費分享給大家
(點選此處加入php進階交流群一起學習交流,11年架構師帶你解讀年薪50萬面試通關秘籍。)如果喜歡我的文章,想與一群資深開發者一起交流學習和指導的話,歡迎加入我的學習交流群
677079770一起學習成長 推薦文章目錄:八重櫻:PHP進階程式員必看知識點:目錄大全(不定期更新)zhuanlan.zhihu.com