MENU

blog
スタッフブログ

dot
いまさら聞けない、抽象クラスとインターフェースとトレイトの違い
技術

いまさら聞けない、抽象クラスとインターフェースとトレイトの違い

こんにちは、クリエイティブSecの長谷川です。

Webシステムや業務システムの開発でPHPを使っていると、オブジェクト指向プログラミング(OOP)の壁に
ぶつかる初心者の方も多いのではないでしょうか。
その中でも特によくある悩みが「抽象クラス」「インターフェース」「トレイト」の使い分けです。

今回は、「いまさら聞けない、抽象クラスとインターフェースとトレイトの違い」と題して
それぞれの役割と使い分けのポイントを、なるべく初心者の方にもわかりやすく、サンプルコードを交えて解説していきます!

オブジェクト指向における3つの仕組みの役割

PHPをはじめとするオブジェクト指向言語では、コードを効率よく書き、保守しやすくするために「クラス」を使います。
しかし、システムが大きくなると「似たような処理をまとめたい」「クラスのルールを統一したい」といった要望が出てきます。

これを解決するのが以下の3つです。

  • 抽象クラス(Abstract Class):共通の「ベース(親)」を作る
  • インターフェース(Interface):外部に対する「契約(ルール)」を決める
  • トレイト(Trait):関係ないクラス間で「処理を使い回す(コピペする)」

一見すると似たようなことができるように感じるかもしれませんが、それぞれ全く異なる目的を持っています。
順番に詳しく見ていきましょう。

1. 抽象クラス(Abstract Class)とは?

抽象クラスは、他のクラスの「共通の親」となるための、未完成の設計図です。

特徴とメリット

  • 「〜である(is-a)」の関係を作るのに向いています(例:犬は「動物」である)。
  • それ自体を直接 new してインスタンス化(実体化)することはできません。
  • 中身のあるメソッド(共通処理)と、中身のないメソッド(子クラスで必ず作らせるルール=抽象メソッド)の両方を持たせることができます。
  • PHPでは、一つのクラスは一つのクラスしか継承できません(単一継承)

共通の処理(デフォルトの動作)を持たせつつ、「ここだけは子クラスで独自の動きを作ってね」と強制したい場合に非常に便利です。

<?php
// 抽象クラス(ベースとなる親)
abstract class Pet {
    // 共通の処理(中身を持てる)
    public function hasFur() {
        return true;
    }

    // 抽象メソッド(子クラスで必ず実装しなければならないルール)
    abstract public function greet();
}

// 抽象クラスを継承した子クラス
class Dog extends Pet {
    // 親から強制されたメソッドを実装する
    public function greet() {
        return "ワンワン!";
    }
}

$dog = new Dog();
echo $dog->greet(); // ワンワン!
echo $dog->hasFur() ? "毛があります" : "毛がありません"; // 毛があります(親のメソッドを利用)

2. インターフェース(Interface)とは?

インターフェースは、クラスに対して「このメソッドを必ず実装してください」という契約(ルール)だけを強制するものです。

そもそもルールだけなら、各クラスで勝手に実装すればいいのでは?

初心者の方が一番疑問に思うのがここです。
「わざわざインターフェースなんて作らなくても、それぞれのクラスで同じ名前のメソッドを作ればいいじゃないか」と思いますよね。

しかし、インターフェースには「違うクラスのオブジェクトでも、同じインターフェースを実装していれば、同じ操作ができることをシステムに保証させる」という強力なメリットがあります。
これを専門用語で「ポリモーフィズム(多様性)」と呼びます。

例えば、クレジットカード決済、PayPal決済など、複数の支払い方法を実装するとします。

<?php
// インターフェース(ルールの定義)
interface PaymentStrategy {
    public function pay($amount); // 処理の中身は書かない
}

// クレジットカード決済
class CreditCardPayment implements PaymentStrategy {
    public function pay($amount) {
        echo "クレジットカードで {$amount} 円支払いました。";
    }
}

// PayPal決済
class PayPalPayment implements PaymentStrategy {
    public function pay($amount) {
        echo "PayPalで {$amount} 円支払いました。";
    }
}

// 支払い処理を行うクラス(カート)
class ShoppingCart {
    // 引数にインターフェースを指定(型ヒンティング)
    // これにより、「PaymentStrategyを実装したクラス」なら何でも受け入れることができる!
    public function checkout($amount, PaymentStrategy $paymentMethod) {
        $paymentMethod->pay($amount);
    }
}

$cart = new ShoppingCart();

// クレジットカードで払う場合
$cart->checkout(1000, new CreditCardPayment()); 

// PayPalで払う場合
$cart->checkout(1500, new PayPalPayment()); 

このように、ShoppingCart クラスは「渡されたオブジェクトが何決済のクラスなのか」を気にする必要がありません。
ただ「PaymentStrategy を実装しているなら、絶対に pay() メソッドを持っているはずだ」と信じて呼び出すだけで済むのです。

後から「PayPay決済」などを追加したくなっても、PaymentStrategy を実装したクラスを新しく作るだけで良く、ShoppingCart 側のコードを書き換える必要がないのが最大の強みです。
これを活用することで、複数人での開発でも「絶対にこのメソッドを実装してね」という契約が明確になり、バグを防ぐことができます。

特徴のまとめ

  • 「〜できる(can-do)」という振る舞いを保証します。
  • 処理の中身を持つことはできません(※メソッドのシグネチャ=名前や引数だけを定義します)。
  • 複数のインターフェースを同時に実装(多重実装)できるのが抽象クラスとの大きな違いです。

(※補足:PHP 8.4からは、インターフェースにプロパティのアクセス要件(Property Hooks)を定義できるようになるなど、より柔軟な契約が可能に進化しています。)

3. トレイト(Trait)とは?

トレイトは、PHP 5.4から導入されたコードを水平方向に再利用(使い回し)するための仕組みです。

特徴とメリット

  • 抽象クラスの「単一継承の制限(親は1つだけ)」を回避するために使われます。
  • 階層構造(親子関係)が全くない複数のクラスに、共通のメソッドを「コピペ」するように注入できます。
  • インターフェースとは違い、具体的な処理の中身を持てます

例えば、「ログを出力する機能」は、ユーザー管理クラスでも、商品管理クラスでも使いたいですが
この2つのクラスに親子関係はありません。
こういう時にトレイトが活躍します。

<?php
// トレイト(使い回したい処理のまとまり)
trait LoggerTrait {
    public function log($message) {
        echo "[LOG]: " . $message . "<br>";
    }
}

// ユーザー管理クラス
class User {
    use LoggerTrait; // トレイトを読み込む

    public function createUser($name) {
        $this->log("ユーザー {$name} を作成しました。");
    }
}

// 商品管理クラス(Userクラスとは全く関係ないクラス)
class Product {
    use LoggerTrait;

    public function createProduct($productName) {
        $this->log("商品 {$productName} を登録しました。");
    }
}

$user = new User();
$user->createUser("Alice"); // [LOG]: ユーザー Alice を作成しました。

$product = new Product();
$product->createProduct("PC"); // [LOG]: 商品 PC を登録しました。

抽象クラス・インターフェース・トレイトの違いと使い分け

それぞれの用途に迷った時は、以下の表と基準を参考にしてみてください。

機能抽象クラス (Abstract Class)インターフェース (Interface)トレイト (Trait)
主な目的共通のベース(親)を作る外部との契約(ルール)を決めるコードを使い回す(再利用)
継承/実装の数1つだけ(単一継承)複数OK(多重実装)複数OK(同時利用可能)
処理の中身書ける書けない(シグネチャのみ)書ける
関係性「〜である」(is-a)「〜できる」(can-do)関係なしに注入可能

使い分けのワンポイントアドバイス

  • 抽象クラス:共通の処理が多く、明確な「親と子」の関係がある場合に使います。システム全体の骨組みを作る時に便利です。
  • インターフェース:「支払いができる」「ログを出力できる」など、異なるクラスに同じ操作方法(ルール)を保証させたい場合に使います。クラス同士を「疎結合」にし、変更に強いシステムを作るための要です。
  • トレイト:親子関係はないけれど、同じ機能をいろんな場所で手軽に使い回したい場合に使います。ただし、使いすぎるとクラスの依存関係が分かりにくくなるため注意が必要です。

さいごに

今回は「抽象クラス」「インターフェース」「トレイト」の違いについて解説しました。

最初は難しく感じるかもしれませんが、「なぜこの仕組みがあるのか?」という背景を知ることで
よりきれいで保守しやすいコードが書けるようになります。
特にインターフェースを使いこなせるようになると、オブジェクト指向の恩恵を強く実感できるはずです。

特定のタイミングでは「これが一番いい」と思える手法でも
システムの成長やPHPのバージョンアップなどの進化の速度が早いために
しばらく後には最適な選択が変わっていることもあります。

それぞれの特性を理解して、可読性や保守性の高いシステム開発に活かしていきたいですね。
それでは今回はこのへんで!

dot
dot
PAGETOP