PHPのinterfaceとclassの違い!オブジェクト指向の設計基礎

[PR]

PHP

PHPでオブジェクト指向設計を行う際、interfaceとclass(特にabstract classを含む)の違いを正しく理解しておくことは非常に重要です。実装の可読性、保守性、拡張性に大きく影響します。多くの開発者が「PHP interface class 違い」で検索する背景には、「どちらを使うべきか」「それぞれの特性とは何か」「最新バージョンで変化はあるか」といった疑問があります。本記事では仕様、用途、メリット・デメリットを最新仕様に沿って解説します。

目次

PHP interface class 違いとは何か

interfaceとclass(通常のclassおよびabstract classを含む)はともにPHPのオブジェクト指向構文における重要な要素であり、異なる目的で使われます。interfaceは「契約」を定義し、classは「実装」を持ち、状態を保持することができます。最新情報として、PHP 8.4以降ではinterfaceにもプロパティ宣言が可能になるなど仕様の変化があります。

interfaceの基本仕様

interfaceはキーワード「interface」を使い、メソッドのシグネチャ(引数や戻り値の型など)を宣言することが目的です。実装(メソッドの中身)は定義できず、メソッドはすべてpublicでなければなりません。プロパティやコンストラクタをinterfaceで定義することはできません(ただし定数は定義可能です)。
最近の仕様ではinterfaceに定数を持たせることができ、加えてPHP 8.4ではinterfaceがpublic読み書き可などのプロパティ宣言を行う機能が追加されました。

class(通常のclassおよびabstract classを含む)の基本仕様

classは実装を持つことができ、プロパティやメソッドの本体を含むことが可能です。通常のclassはインスタンス化が可能ですが、abstract classは少なくとも一つのabstractメソッドまたはプロパティを持つクラスであり、自身ではインスタンス化できません。プロパティの可視性(public・protected・private)、静的メンバー、コンストラクタなど多くの実装要素を自由に持てます。PHP 8.4以降、abstract classでもabstractプロパティの宣言ができるようになりました。

interfaceとclassを区別する理由(設計上の意図)

interfaceは異なるクラスが同じ振る舞いを持つことを保証する契約であり、コードの疎結合を促進します。classは状態を持ち、振る舞いの共有や継承関係を構築するためのものです。どちらを選択するかは「何を共有したいか」「何を強制したいか」によります。interfaceは複数の実装がありうる振る舞いを統一するため、classは共通する処理やデータを持つ親の役割を果たします。

interfaceとclassの違いを仕様で比較

仕様の違いを表で整理することで理解が深まります。下記の比較表は最新仕様に基づいており、特にPHP 8.0〜8.4での変更を反映しています。

項目 interface class / abstract class
インスタンス化 できない 通常のclassは可能、abstract classはできない
メソッドの実装 本体なしで宣言のみ(abstract的) 具体的な実装を持てる
プロパティ 原則なし。定数あり。PHP 8.4でpublic読み書き可プロパティ宣言可能
プロパティは読み取り・書き込みの可否指定が必要
自由に宣言可能。可視性あり。staticも使用可
可視性修飾子 メソッドはすべてpublic public/protected/private が使用可能
継承・多重実装 複数のinterfaceを実装可能
interface同士のextendsあり
1つの親classのみ継承可能
abstract classを含むクラス階層
コンストラクタ 定義不可(設計上契約に影響するため避けられる) 通常のclassで定義可能。abstract classも持てる
目的と用途 振る舞いの契約,ポリモーフィズムを実現 状態管理,共通機能の共有,具象クラスへの基盤

最新仕様でのinterfaceの拡張(PHP 8.4の変更点)

interfaceに対する最新の仕様改善があり、PHP 8.4でinterfaceがプロパティ宣言をサポートするようになりました。読み取り可または書き込み可、あるいは両方を有するプロパティを指定でき、publicな読み書き可能なプロパティのみ許可されます。従来interfaceにはプロパティを持たせることができなかったため、大きな変更です。
ただし、書き込み可能となるプロパティをreadonlyなclassプロパティで満たすことはできないという制約などもあります。

abstract classの仕様(最新対応)

abstract classは少なくとも一つのabstractメソッドまたはabstractプロパティを含むクラスであり、自身でインスタンス化できません。PHP 8.4ではabstract classでも抽象プロパティを宣言でき、publicまたはprotectedの可視性を指定できるようになりました。これにより実装クラスで抽象プロパティのget/write要件を満たす形でプロパティまたはプロパティフックを実装する選択肢が増えて設計が柔軟になりました。

interfaceとclassの使い分け方と設計のベストプラクティス

理解を深めた上で、「いつinterfaceを使い」「いつclass(またはabstract class)を使うべきか」という方針を知っておくことは実践的に役立ちます。ここでは設計観点からの使い分けと注意点を示します。

どの場合にinterfaceを選ぶべきか

以下のような状況ではinterfaceを選択するのが有効です。複数クラスに共通の振る舞いを保証したい場合、または異なる継承関係にあるクラスに対して同じAPIを持たせたい場合などです。たとえば、キャッシュ可能なもの、描画可能なものなど異なるコンポーネントに共通する契約を定義する際にinterfaceが有効です。可読性・依存性注入・テスト容易性にも有利です。

どの場合にclass(abstract含む)を選ぶべきか

共通のプロパティや処理を持たせたい場合、抽象クラスを使った継承でコードを再利用したい場合にclassが適しています。例えば、共有ロジック(例:ログ記録、基本的なCRUD処理など)を親クラスにまとめて子クラスに継承させるという設計が典型です。抽象クラスは共通メソッドを定義でき,具象クラスで必要な部分を実装させる設計が有効です。

混合利用:interfaceとabstract classを併用する設計

interfaceとabstract classを組み合わせることで、設計の柔軟性と再利用性を最大化できます。まず振る舞いの契約をinterfaceで定義し、それをabstract classがimplementsして一部を実装、最終的に具体的なclassが継承して利用するパターンです。こうすることで、contract(契約)の保証と、共通実装の共有が両立できます。

interface class 違いを具体例で理解する

このセクションでは、実際のコード例を通してinterfaceとclassの違いを具体的に見てみます。設計の意図、対象、継承・実装の流れを可視化することで、頭でなく手を動かした理解につながります。

interfaceを使った例:振る舞いの契約のみを定義

以下は、描画可能なオブジェクトが持つ振る舞いを定義したinterfaceと、それを実装する複数のクラスの例です。interfaceではメソッドのみを宣言し、実装はそれぞれのclassで行います。


interface Drawable {
    public function draw(): string;
    public function resize(int $width, int $height): void;
}

class Circle implements Drawable {
    private float $radius;
    public function __construct(float $radius) {
        $this->radius = $radius;
    }
    public function draw(): string {
        return "Circle with radius {$this->radius}";
    }
    public function resize(int $width, int $height): void {
        // resize logic
    }
}

class Square implements Drawable {
    private float $side;
    public function __construct(float $side) {
        $this->side = $side;
    }
    public function draw(): string {
        return "Square with side {$this->side}";
    }
    public function resize(int $width, int $height): void {
        // resize logic
    }
}

abstract classを使った例:共通処理と契約を含む基底クラス

次はabstract classを使い、一部共通処理を持たせつつ具象クラスで固有処理を実装する例です。abstract classは振る舞いの契約(abstractメソッド)とともに実装を含みます。


interface Logger {
    public function log(string $message): void;
}

abstract class BaseProcessor implements Logger {
    protected string $name;
    public function __construct(string $name) {
        $this->name = $name;
    }
    public function log(string $message): void {
        echo "[{$this->name}] {$message}n";
    }
    abstract public function process(array $data): array;
}

class CsvProcessor extends BaseProcessor {
    public function process(array $data): array {
        // CSV処理
        $this->log("Starting CSV processing");
        // 実装コード
        $this->log("Finished CSV processing");
        return $data;
    }
}

interfaceとclassでよくある誤解とその修正

実際の現場でinterfaceとclassの違いを誤解しているケースは少なくありません。ここでは典型的な間違いと、それを避けるためのポイントを整理します。

interfaceにプロパティを入れたいと思ってしまう誤解

従来、interfaceにはプロパティを定義できないという仕様でした。そのため「共通プロパティはinterfaceに書ける」と思う場合があります。しかし最新仕様ではinterfaceにプロパティ宣言が可能ですが、可視性はpublic読み書き可能なもののみであり、readonlyの両立は制限があります。この点を誤ると、コードが意図しないアクセス許可を持ってしまうことがあります。

abstract classとinterfaceの使い分けを曖昧にする誤解

「interfaceはabstract classとほとんど同じだからどちらでもいい」と考えるのは誤りです。interfaceは契約を保証するもの、abstract classは実装の共有を前提とするものです。interfaceだけで共通処理を実装しようとすると冗長になり、abstract classを濫用すると継承の複雑化が起きる可能性があります。

継承と実装の制約の理解不足による誤り

PHPではクラスは一つしか継承できませんが、複数のinterfaceを実装することができます。これにより複雑な継承構造を避けることができます。abstract classは単一継承しかできないので、設計段階での意図しない制約につながります。適切な継承構造を設計することが重要です。

interface class 違いをケーススタディで検証

ここでは実際の設計シナリオを通して、interface・abstract class・classを使い分ける判断方法と実装例を解説します。具体的なユースケースから得られる知見が理解を深めます。

ケース1:複数の異なる形を描画するプラグインシステム

プラグインとして様々な描画器(Circle, Square, Triangleなど)を受け入れるシステムを考えます。各描画器は共通API(描画・サイズ変更など)を持つ必要があり、将来別の描画方法が追加される可能性があります。この場合、interfaceを使って契約を定義し、各実装をclassとして実装するのが最適です。

ケース2:共通処理が多いデータ処理パイプライン

データを処理するクラス群で、ログ記録や入力検証など共通処理が明らかにある場合、abstract classをベースに共通機能をまとめ、具象クラスで具体処理を実装することが効率的です。interfaceだけで行うと重複コードが増えます。

ケース3:外部APIやライブラリ間の依存を減らしたい場合

異なるライブラリが共通のインターフェースを提供し、実装を差し替え可能にすることで依存性注入やテストが容易になります。この場合interfaceを使って実装を独立させ、テスト用のスタブやモックもinterfaceに基づいて実装できます。

interfaceとclassに関するパフォーマンスと互換性の注意点

設計だけでなく、実行環境や将来のPHPバージョンとの互換性も考慮に入れることが多くの現場で求められます。interfaceとclassの設計によってPHPの型チェックやエラー処理、静的解析ツールとの相性が変わるため注意が必要です。

型宣言や戻り値型の互換性

interfaceで宣言されたメソッドのシグネチャ(引数の型・戻り値の型など)を実装クラスで一致させる必要があります。PHPは型システムの強化が進んでおり、引数や戻り値の型が一致しないと致命的なエラーになることがあります。特にinterfaceやabstract classの変更は既存の実装クラスに波及します。

静的解析とドキュメンテーションツールとの統合

interfaceを利用して契約を明示すると、静的解析ツールやIDEの補完機能が働きやすくなります。classで共通処理をまとめると変更時にドキュメンテーションを更新しやすくなりますが、継承の深さが深くなると依存関係が見えづらくなるため注意が必要です。

将来のPHPバージョンでの仕様変更への対応

PHPの仕様は進化しており、PHP 8.4でinterfaceがプロパティ宣言をサポートするなどの変更があったように、将来のバージョンでもinterface/classの機能に追加や制約の変更がある可能性があります。設計の際には最新のリリースノートや仕様を確認し、柔軟性を持たせる設計を心がけることが望ましいです。

まとめ

PHPにおけるinterfaceとclassの違いを整理すると、interfaceは契約としての振る舞いを保証するために使い、classは状態や共通処理を持つために使います。abstract class を含めることで、設計の共有性と厳格さを両立できます。

最新仕様により、interfaceにプロパティ宣言が加わるなど変化がありますが、基本的な違いは契約 vs 実装、multiple implements vs single extends、可視性やプロパティの有無といった点です。この知識を設計に活かせば、保守性・拡張性・予測容易性が高まります。

関連記事

特集記事

コメント

この記事へのトラックバックはありません。

TOP
CLOSE