PHP中的抽象類、介面與性狀

失色天空發表於2021-12-24

網路上對抽象類、介面、性狀的總結都是停留在概念層面,程式碼也都是相似的。我覺得大部分作者自己都不知道怎麼使用,這裡從程式碼的層面簡單總結一下它們常見使用場景,加深理解,記住一切皆型別就對了。

一、介面(interface)

對具體的實現類的型別進行約束,只要實現了介面的方法,原來的類就會多一種型別。介面不能直接例項化,也不能包含常量,可以繼承。如果你編寫的類有大量相同的行為,那麼你可以把它定義成介面。

  • 實現介面

      <?php
    
      // 介面
      interface Animal {
    
          public function eat();
      }
    
      // 實現介面的具體類
      class Dog implements Animal {
    
          public function eat() {
              print "dog eat \n";
          }
      }
    
      // 例項化類
      $dog = new Dog();
      var_dump($dog instanceof Animal);
      var_dump($dog instanceof Dog);
      // 輸出:
      # bool(true)
      # bool(true)

    可以看出,當一個類實現了介面以後,原來的類就會多一種型別,可以利用介面的特性進行引數約束和型別轉換(協變和逆變)。

  • 使用場景

    實現多型、里氏替換、面向介面程式設計。

      <?php
    
      // 介面
      interface Animal {
    
          public function eat();
      }
    
      // 實現介面的具體類
      class Dog implements Animal {
    
          public function eat() {
              print "dog eat \n";
          }
      }
    
      // 實現介面的具體類
      class Cat implements Animal {
    
          public function eat() {
              print "cat eat \n";
          }
      }
    
      function call(Animal $animal) {
          print $animal->eat();
      }
    
      call(new Dog());
      call(new Cat());

    可以看出,對於同一個函式call,引數設定為介面型別(Animal型別),任何實現介面的類(Cat、Dog)都會多出一種型別(Animal),所以函式能正常接收和呼叫,實現多型和里氏替換的效果。

二、抽象類(abstract class)

對共性保留,對差異開放,即求同存異,只要實現了抽象類的抽象方法,原來的類就會多一種型別,抽象類不能被直接例項化,可以繼承。如果你編寫的類有大量相同的程式碼,可以提取到抽象類中複用。

  • 實現抽象類

    <?php
      // 抽象類
      abstract class Animal {
    
          public function animalShout() {
              print "hello, ";
              $this->shout();
          }
    
          abstract function shout();
      }
    
      // 實現抽象類的具體類
      class Dog extends Animal {
    
          function shout() {
              print "dog shout \n";
          }
      }
    
      // 實現抽象類的具體類
      class Cat extends Animal {
    
          function shout() {
              print "cat shout \n";
          }
      }
    
      $dog = new Dog();
      $dog->animalShout();
      $cat = new Cat();
      $cat->animalShout();
      # 輸出
      # hello, dog shout
      # hello, cat shout

    可以看出,Cat類和Dog類都呼叫了同一個方法animalShout列印hello(求同),然後各自實現了各自的shout方法(存異),可以使用抽象類的這個特性實現多型和里氏替換的效果。

三、性狀(trait)

將公共的程式碼提取出來,在多個地方進行嵌入複用。性狀不能被直接例項化,性狀中可以巢狀性狀。如果你編寫的業務類中有和業務無關的程式碼,比如:實現單例和一些工具方法,你可以提取到性狀中複用。

  • 使用性狀
    <?php
    // 實現單例
    trait Singleton {
        private static $_instance = null;

        /**
         * @return static
         */
        public static function getInstance() {
            self::$_instance || self::$_instance = new self();
            return self::$_instance;
        }

        private function __clone() {

        }

        private function __construct() {
        }
    }

    class User {
        // 使用性狀
        use Singleton;

        public $age;


        /**
         * @param $age
         * @return $this
         */
        public function setAge($age) {
            $this->age = $age;
            return $this;
        }

        /**
         * @return mixed
         */
        public function getAge() {
            return $this->age;
        }
    }

    print User::getInstance()->setAge(20)->getAge();
    print User::getInstance()->setAge(21)->getAge();
    # 輸出
    # 20
    # 21

可以看出,只需要嵌入性狀,就可以在類中呼叫性狀的方法,實現單例,任何需要單例的地方只需要嵌入就行,避免寫重複的程式碼。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章