PHP中的抽象类、接口与性状

网络上对抽象类、接口、性状的总结都是停留在概念层面,代码也都是相似的。我觉得大部分作者自己都不知道怎么使用,这里从代码的层面简单总结一下它们常见使用场景,加深理解,记住一切皆类型就对了。

一、接口(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 协议》,转载必须注明作者和本文链接
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
讨论数量: 3

为什么不用Trait呢

4周前 评论

接口不能直接实例化,也不能包含常量,可以继承。

PHP 手册 语言参考 类与对象 对象接口:

接口中也可以定义常量。接口常量和类常量的使用完全相同, 在 PHP 8.1.0 之前 不能被子类或子接口所覆盖。

1周前 评论
失色天空 (楼主) 1周前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!