2021年3月14日 星期日

PHP 的 Trait 用法

設定目標:
  • 了解 PHP 如何使用 Trait 來解決跨類別問題

Trait 概念與實作
  1. 特徵(Trait)
    • Trait 與抽象類別相似,無法使用 new 來產生物件(被實例化)!
    • 因為 PHP 只能單一繼承,所以無法跨多個類別使用這些類別內的方法!
    • 為了減少程式碼的重複撰寫,所以 PHP 使用 Trait 來克服這個問題!
    • 使用 Trait 可以讓 PHP 實現單一類別(Singleton)的使用!
    • 單一 Trait 使用範例 :
      • oneTrait.php
        <?php
        class Base {
          public function sayHello() {
            echo 'Hello ';
          }
        }
        trait SayWorld {
          public function sayHello() {
            parent::sayHello();
            echo 'World!';
          }
        }
        class MyHelloWorld extends Base {
          use SayWorld;
        }
        
        $o = new MyHelloWorld();
        $o->sayHello();
        ?>
        
    • 多個 Trait 使用範例 :
      • multiTrait.php
        <?php
        trait Hello {
          public function sayHello() {
            echo 'Hello ';
          }
        }
        trait World {
          public function sayWorld() {
            echo 'World';
          }
        }
        class MyHelloWorld {
          use Hello, World;
          public function sayExclamationMark() {
            echo '!';
          }
        }
        $o = new MyHelloWorld();
        $o->sayHello();
        $o->sayWorld();
        $o->sayExclamationMark();
        ?>
        
      • integrateTrait.php
        <?php
        trait Hello {
          public function sayHello() {
            echo 'Hello ';
          }
        }
        trait World {
          public function sayWorld() {
            echo 'World';
          }
        }
        trait HelloWorld{
          use Hello, World;
        }
        class MyWorld{
          use HelloWorld;
        }
        $world = new MyWorld();
        echo $world->sayHello() . " " . $world->sayWorld(); //Hello World
        ?>
        
    • 優先序問題
      • Trait 內可以使用 overwrite 功能來覆蓋父類別有的功能!
      • 優先序的範例 : orderTrait.php
        <?php
        trait Hello{
          function sayHello() {
            return "Hello";
          }
          function sayWorld() {
            return "Trait World";
          }
          function sayHelloWorld() {
            echo $this->sayHello() . " " . $this->sayWorld();
          }
          function sayBaseWorld() {
            echo $this->sayHello() . " " . parent::sayWorld();
          }
        }
        
        class Base{
          function sayWorld(){
            return "Base World";
          }
        }
        class HelloWorld extends Base{
          use Hello;
          function sayWorld() {
            return "World";
          }
        }
        $h =  new HelloWorld();
        $h->sayHelloWorld(); // Hello World
        ?>
        
    • Trait 的衝突與別名 : 使用 insteadof 與 as
      • 利用 insteadof 來解決不同 Trait 但相同方法名稱的問題
      • 例 : 有一衝突的範例 confuse.php
        <?php
        trait Game{
          function play() {
            echo "Playing a game";
          }
        }
        trait Music{
          function play() {
            echo "Playing music";
          }
        }
        
        class Player{
          use Game, Music;
        }
        $player = new Player();
        $player->play();
        ?>
        
        PS: 以上範例有兩個 Trait ,有相同的方法名稱 play()!在使用當下,就會發生衝突!
      • 修改後的 confuse.php 如下:
        <?php
        trait Game{
          function play() {
            echo "Playing a game";
          }
        }
        trait Music{
          function play() {
            echo "Playing music";
          }
        }
        
        class Player{
          use Game, Music{
            //將 Game 的 play 別名成 gamePlay
            Game::play as gamePlay;
            //使用 Music 的 play 功能,取消 Game 的 play 功能!
            Music::play insteadof Game;
          }
        }
        $player = new Player();
        $player->play();
        $player->gamePlay();
        ?>
        
  2. Trait 其它功能
    • 利用 Trait 獨特的使用功能,可以取得 private 權限的類別屬性值
      • 例 : getPritrait.php
        <?php
        trait Message {
          function alert() {
            echo $this->message;
          }
        }
        class Messenger {
          use Message;
          private $message = "This is a message";
        }
        
        $messenger = new Messenger;
        $messenger->alert(); //This is a message
        ?>
        
    • 利用 Trait 內可抽象化方法,強迫使用該 Trait 的類別,實作該抽象方法!
      • 例 : abtractTrait.php
        <?php
        trait Message {
          private $message;
              
          function alert() {
            $this->define();
            echo $this->message;
          }
          abstract function define();
        }
        
        class Messenger {
          use Message;
          function define() {
            $this->message = "Custom Message";
          }
        }
        
        $messenger = new Messenger;
        $messenger->alert(); //Custom Message
        ?>
        
    • Trait 的 use 與類別導入的 use 是不同的功能
      • Trait 的 use : 置放於 class 內部!
      • 類別導入的 use : 置放於 class 外部!

本章練習:
  • 修改你的 Human 類別,讓它可以使用 Bird 類別中的 fly 方法!