首页 / PHP 入门教程 / PHP 抽象类、接口与 Trait

PHP 入门教程

PHP 抽象类、接口与 Trait

本教程共 65 篇 · 第 21 篇 · 更新于 2026-07-24 · 约 5 分钟阅读

PHPPHP8抽象类接口Trait多态代码复用

21. PHP 抽象类、接口与 Trait

本节目标:掌握抽象类、接口和 Trait 三种代码复用机制。学完你能设计灵活的类结构,实现多态和代码混入。

21.1 抽象类

抽象类是不能直接实例化的类,只能被继承。它可以包含抽象方法(只有声明,没有实现):

<?php
abstract class Animal {
    protected string $name;
    
    public function __construct(string $name) {
        $this->name = $name;
    }
    
    // 普通方法
    public function sleep(): string {
        return "{$this->name}在睡觉";
    }
    
    // 抽象方法:子类必须实现
    abstract public function speak(): string;
}

class Dog extends Animal {
    public function speak(): string {
        return "{$this->name}说:汪汪汪";
    }
}

class Cat extends Animal {
    public function speak(): string {
        return "{$this->name}说:喵喵喵";
    }
}

// $animal = new Animal("动物");  // 错误!不能实例化抽象类
$dog = new Dog("大黄");
echo $dog->speak();   // 大黄说:汪汪汪
echo $dog->sleep();   // 大黄在睡觉
?>
Note

抽象类适合”部分实现”的场景:父类提供通用代码,子类完成特定逻辑。

21.2 接口

接口只声明方法签名,不包含实现。类通过 implements 实现接口:

<?php
// 定义接口
interface LoggerInterface {
    public function log(string $message): void;
    public function error(string $message): void;
}

interface CacheInterface {
    public function get(string $key): mixed;
    public function set(string $key, mixed $value): void;
}

// 实现多个接口
class FileLogger implements LoggerInterface {
    public function log(string $message): void {
        file_put_contents('app.log', $message . "\n", FILE_APPEND);
    }
    
    public function error(string $message): void {
        $this->log("[ERROR] $message");
    }
}

class RedisCache implements CacheInterface {
    public function get(string $key): mixed {
        // 从 Redis 读取
        return null;
    }
    
    public function set(string $key, mixed $value): void {
        // 写入 Redis
    }
}
?>

接口继承接口

<?php
interface Readable {
    public function read(): string;
}

interface Writable {
    public function write(string $data): void;
}

// 接口可以继承多个接口
interface Stream extends Readable, Writable {
    public function close(): void;
}

class FileStream implements Stream {
    public function read(): string { return ""; }
    public function write(string $data): void {}
    public function close(): void {}
}
?>

21.3 抽象类 vs 接口

特性抽象类接口
实例化不能不能
包含属性可以只能有常量
方法实现可以不能(只能声明方法签名)
继承/实现单继承多实现
构造函数可以有不能
<?php
// 选择原则:
// - "是一种"关系 + 共享代码 = 抽象类
// - "能做什么"关系 + 纯契约 = 接口

// 抽象类示例:共享代码
abstract class Controller {
    protected function render(string $view): string {
        return "渲染视图: $view";
    }
}

// 接口示例:定义能力
interface Authenticatable {
    public function login(string $user, string $pass): bool;
    public function logout(): void;
}
?>

21.4 Trait

PHP 不支持多重继承,Trait 提供了一种代码复用机制:

<?php
trait Timestampable {
    public ?DateTime $createdAt = null;
    public ?DateTime $updatedAt = null;
    
    public function setCreatedAt(): void {
        $this->createdAt = new DateTime();
    }
    
    public function setUpdatedAt(): void {
        $this->updatedAt = new DateTime();
    }
}

trait Loggable {
    public function log(string $action): void {
        echo "[LOG] $action at " . date('Y-m-d H:i:s') . "\n";
    }
}

// 在一个类中使用多个 Trait
class Article {
    use Timestampable, Loggable;
    
    public string $title;
    
    public function publish(): void {
        $this->setCreatedAt();
        $this->log("发布文章: {$this->title}");
    }
}

$article = new Article();
$article->title = "PHP 入门";
$article->publish();
?>

21.5 解决 Trait 冲突

当多个 Trait 有同名方法时,需要显式解决冲突:

<?php
trait TraitA {
    public function sayHello(): string {
        return "Hello from A";
    }
    
    public function sayGoodbye(): string {
        return "Goodbye from A";
    }
}

trait TraitB {
    public function sayHello(): string {
        return "Hello from B";
    }
}

class MyClass {
    use TraitA, TraitB {
        // 使用 TraitA 的 sayHello,而不是 TraitB 的
        TraitA::sayHello insteadof TraitB;
        
        // 给 TraitB 的 sayHello 起别名
        TraitB::sayHello as sayHelloB;
    }
}

$obj = new MyClass();
echo $obj->sayHello();    // Hello from A
echo $obj->sayHelloB();   // Hello from B
echo $obj->sayGoodbye();  // Goodbye from A
?>

21.6 Trait 修改访问控制

<?php
trait Utility {
    public function helper(): string {
        return "帮助方法";
    }
}

class Service {
    use Utility {
        // 将 public 改为 private
        helper as private;
    }
    
    public function doWork(): string {
        return $this->helper();  // 类内部可以调用
    }
}

$service = new Service();
echo $service->doWork();   // 帮助方法
// echo $service->helper();  // 错误!现在是 private
?>

21.7 接口的常用用法

<?php
// 接口可以定义常量
interface Configurable {
    public const VERSION = "1.0";
}

// 使用 interface 约束类型
interface LoggerInterface {
    public function log(string $message): void;
}

// 用 instanceof 检查接口
if ($obj instanceof LoggerInterface) {
    $obj->log("就绪");
}

// 一个类可以实现多个接口
class FileLogger implements LoggerInterface, Configurable {
    public function log(string $message): void {
        echo "[LOG] $message (v" . self::VERSION . ")\n";
    }
}
?>

21.8 何时使用什么

场景选择
共享代码 + 强制子类实现某些方法抽象类
定义一组能力/契约,不关心实现接口
水平复用代码(跨类层次)Trait
需要多继承效果接口 + Trait

来源:参考了 w3cschool「PHP 面向对象」、runoob「PHP 面向对象」以及 php.net 官方文档,综合改写后所得。