はじめに
PHPでオブジェクト指向プログラミングを学ぶとき、「インターフェース」と「抽象クラス」の違いに悩む方は多いです。どちらも「継承先のクラスに実装を強制する」仕組みですが、設計上の役割は大きく異なります。
この記事では、それぞれの特徴と使いどころをコード例とともに丁寧に解説します。
インターフェースとは
インターフェース(interface) は、クラスが「何ができるか」を定義する契約書のようなものです。メソッドのシグネチャ(名前・引数・戻り値の型)だけを宣言し、具体的な実装は持ちません。
<?php
interface Notifiable
{
public function send(string $message): bool;
public function getChannel(): string;
}
インターフェースを実装するクラスは、implements キーワードを使い、宣言されたすべてのメソッドを実装する必要があります。
<?php
class EmailNotifier implements Notifiable
{
public function __construct(private string $to) {}
public function send(string $message): bool
{
// メール送信処理(省略)
echo "メール送信先: {$this->to} / メッセージ: {$message}" . PHP_EOL;
return true;
}
public function getChannel(): string
{
return 'email';
}
}
class SlackNotifier implements Notifiable
{
public function __construct(private string $webhook) {}
public function send(string $message): bool
{
echo "Slack通知: {$this->webhook} / メッセージ: {$message}" . PHP_EOL;
return true;
}
public function getChannel(): string
{
return 'slack';
}
}
インターフェースの多重実装
PHPはクラスの多重継承をサポートしませんが、インターフェースは複数実装できます。
<?php
interface Loggable
{
public function log(string $level, string $message): void;
}
class EmailNotifier implements Notifiable, Loggable
{
public function send(string $message): bool { /* ... */ return true; }
public function getChannel(): string { return 'email'; }
public function log(string $level, string $message): void
{
echo "[{$level}] {$message}" . PHP_EOL;
}
}
抽象クラスとは
抽象クラス(abstract class) は、インターフェースと通常のクラスの中間に位置します。抽象メソッド(実装なし)と通常のメソッド(実装あり)を混在させることができます。
<?php
abstract class BaseNotifier
{
// 共通の実装を持つメソッド
public function sendWithLog(string $message): bool
{
echo "[LOG] 送信開始: チャンネル={$this->getChannel()}" . PHP_EOL;
$result = $this->send($message);
echo "[LOG] 送信結果: " . ($result ? '成功' : '失敗') . PHP_EOL;
return $result;
}
// サブクラスに実装を強制する抽象メソッド
abstract public function send(string $message): bool;
abstract public function getChannel(): string;
}
サブクラスは extends で継承し、抽象メソッドをすべて実装します。
<?php
class SmsNotifier extends BaseNotifier
{
public function __construct(private string $phoneNumber) {}
public function send(string $message): bool
{
echo "SMS送信先: {$this->phoneNumber} / メッセージ: {$message}" . PHP_EOL;
return true;
}
public function getChannel(): string
{
return 'sms';
}
}
// 使用例
$notifier = new SmsNotifier('090-1234-5678');
$notifier->sendWithLog('登録が完了しました');
// [LOG] 送信開始: チャンネル=sms
// SMS送信先: 090-1234-5678 / メッセージ: 登録が完了しました
// [LOG] 送信結果: 成功
インターフェース vs 抽象クラス:どう使い分けるか
| 比較項目 | インターフェース | 抽象クラス |
|---|---|---|
| 実装の共有 | ❌ できない | ✅ できる |
| 多重継承 | ✅ 複数実装可 | ❌ 単一継承のみ |
| コンストラクタ | ❌ 持てない | ✅ 持てる |
| プロパティ | ❌ 持てない | ✅ 持てる |
| 主な用途 | 「できること」の契約 | 共通処理のテンプレート |
使い分けの指針
- インターフェース:異なる系統のクラスに同じ「能力」を持たせたいとき。たとえば
Notifiable(通知できる)、Serializable(シリアライズできる)など。 - 抽象クラス:共通の処理フローをテンプレートとして提供し、一部の処理だけをサブクラスに委ねたいとき。
実践:インターフェースと抽象クラスを組み合わせる
現実の設計では、両者を組み合わせるのが効果的です。
<?php
interface Notifiable
{
public function send(string $message): bool;
public function getChannel(): string;
}
abstract class BaseNotifier implements Notifiable
{
public function sendWithLog(string $message): bool
{
echo "[LOG] [{$this->getChannel()}] 送信開始" . PHP_EOL;
$result = $this->send($message);
echo "[LOG] 送信" . ($result ? '成功' : '失敗') . PHP_EOL;
return $result;
}
}
class PushNotifier extends BaseNotifier
{
public function send(string $message): bool
{
echo "プッシュ通知: {$message}" . PHP_EOL;
return true;
}
public function getChannel(): string
{
return 'push';
}
}
// 型ヒントにはインターフェースを使う
function notify(Notifiable $notifier, string $message): void
{
$notifier->send($message);
}
$push = new PushNotifier();
notify($push, 'お知らせがあります');
// プッシュ通知: お知らせがあります
型ヒントに Notifiable インターフェースを使うことで、EmailNotifier でも SlackNotifier でも同じ関数に渡せます。これが依存を抽象に向ける設計の基本です。
まとめ
- インターフェースは「何ができるか」の契約を定義し、複数実装できる
- 抽象クラスは共通処理をまとめ、サブクラスに差分だけを実装させる
- 両者を組み合わせることで、柔軟で保守性の高い設計が実現できる
最初は難しく感じるかもしれませんが、「インターフェースは約束、抽象クラスはテンプレート」と覚えておくと判断しやすくなります。ぜひ自分のコードで試してみてください。