PHP 抽象類別 (Abstract Class)

抽象類別是不能被實例化的類別,用來作為其他類別的基礎。抽象類別可以包含抽象方法(沒有實作)和一般方法。

定義抽象類別

<?php
abstract class Shape {
    abstract public function area(): float;
    abstract public function perimeter(): float;
    
    public function describe(): string {
        return "這是一個形狀";
    }
}

class Rectangle extends Shape {
    public function __construct(
        private float $width,
        private float $height
    ) {}
    
    public function area(): float {
        return $this->width * $this->height;
    }
    
    public function perimeter(): float {
        return 2 * ($this->width + $this->height);
    }
}

// $shape = new Shape();  // 錯誤:不能實例化抽象類別
$rect = new Rectangle(5, 3);
echo $rect->area();       // 15
echo $rect->perimeter();  // 16
?>

抽象方法

抽象方法只有簽名,沒有實作:

<?php
abstract class Database {
    abstract public function connect(): void;
    abstract public function query(string $sql): array;
    abstract public function close(): void;
    
    // 非抽象方法可以有實作
    public function execute(string $sql): bool {
        $this->query($sql);
        return true;
    }
}

class MySQLDatabase extends Database {
    private $connection;
    
    public function connect(): void {
        $this->connection = mysqli_connect('localhost', 'user', 'pass', 'db');
    }
    
    public function query(string $sql): array {
        $result = mysqli_query($this->connection, $sql);
        return mysqli_fetch_all($result, MYSQLI_ASSOC);
    }
    
    public function close(): void {
        mysqli_close($this->connection);
    }
}
?>

抽象類別 vs 介面

特性抽象類別介面
實例化不可不可
方法實作可以有不可(PHP 8+ 可有預設)
屬性可以有只能有常數
建構子可以有不可
繼承單一多重
存取修飾符可以有預設 public
<?php
// 抽象類別:共享實作
abstract class Animal {
    protected string $name;
    
    public function __construct(string $name) {
        $this->name = $name;
    }
    
    abstract public function speak(): string;
    
    public function getName(): string {
        return $this->name;
    }
}

// 介面:定義能力
interface Swimmable {
    public function swim(): void;
}

class Duck extends Animal implements Swimmable {
    public function speak(): string {
        return "Quack!";
    }
    
    public function swim(): void {
        echo "{$this->name} is swimming\n";
    }
}
?>

模板方法模式

<?php
abstract class DataExporter {
    // 模板方法
    final public function export(array $data): string {
        $this->validate($data);
        $formatted = $this->format($data);
        return $this->output($formatted);
    }
    
    protected function validate(array $data): void {
        if (empty($data)) {
            throw new InvalidArgumentException("Data cannot be empty");
        }
    }
    
    abstract protected function format(array $data): string;
    abstract protected function output(string $content): string;
}

class JsonExporter extends DataExporter {
    protected function format(array $data): string {
        return json_encode($data);
    }
    
    protected function output(string $content): string {
        return $content;
    }
}

class CsvExporter extends DataExporter {
    protected function format(array $data): string {
        $output = [];
        foreach ($data as $row) {
            $output[] = implode(',', $row);
        }
        return implode("\n", $output);
    }
    
    protected function output(string $content): string {
        return $content;
    }
}
?>

實際範例

<?php
abstract class Controller {
    protected array $data = [];
    
    abstract protected function handle(): void;
    
    public function execute(): string {
        $this->before();
        $this->handle();
        $this->after();
        return $this->render();
    }
    
    protected function before(): void {
        // 前置處理
    }
    
    protected function after(): void {
        // 後置處理
    }
    
    protected function render(): string {
        return json_encode($this->data);
    }
}

class UserController extends Controller {
    protected function handle(): void {
        $this->data = [
            'users' => [
                ['id' => 1, 'name' => 'Alice'],
                ['id' => 2, 'name' => 'Bob'],
            ]
        ];
    }
}

$controller = new UserController();
echo $controller->execute();
?>