トレイト vs 継承
CoddyのPHPジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 55/91。
トレイトの仕組みを理解したところで、トレイトと継承のどちらを使うべきかを明確にしましょう。どちらもコードの再利用を可能にしますが、それぞれ目的が異なり、明確な特徴があります。
継承は「is-a」関係を構築します。Animalを拡張するDogクラスは、犬が動物である(is an)ことを意味します。子クラスは親のアイデンティティを継承し、親の型が期待される場所であればどこでも使用できます。
トレイトは「has-a」機能を提供します。クラスが Loggable トレイトを使用すると、そのクラスはロギング機能を持ちますが、ロガーそのものになるわけではありません。トレイトは、クラスが根本的に何であるかを変えることなく、振る舞いを追加します。
<?php
// 継承: Dog は Animal です
class Animal {
public function breathe(): void {
echo "Breathing";
}
}
class Dog extends Animal {
public function bark(): void {
echo "Woof!";
}
}
// トレイト: Dog はロギング機能を保持しています
trait Loggable {
public function log(string $msg): void {
echo "Log: $msg";
}
}
class Cat extends Animal {
use Loggable;
}
覚えておくべき主な違い:
| 側面 | 継承 | トレイト |
|---|---|---|
| 関係 | is-a | has-a(機能の保持) |
| 制限 | 単一の親のみ | 複数のトレイトが可能 |
| 型チェック | instanceof で動作する | 型のアイデンティティなし |
| 最適な用途 | 階層関係 | 関連のないクラス間での動作の共有 |
クラスが共通のアイデンティティを共有する場合は、継承を使用してください。関連のないクラスが、同じ継承ツリーの一部になることなく同じ機能を必要とする場合は、トレイトを使用してください。
チャレンジ
簡単継承とトレイトをいつ使用すべきかを示す車両システムを構築しましょう。継承を通じて共通のアイデンティティを共有する車両の階層を作成し、トレイトを通じて横断的な機能を追加します。
コードを4つのファイルに分けて構成します:
Trackable.php— GPS追跡機能を提供するTrackableという名前のトレイトを作成します。このトレイトには以下を含める必要があります:- プライベートプロパティ
$location(string) - 現在の場所を保存するメソッド
setLocation(string $location) "Location: " . $this->locationを返すメソッドgetLocation()
- プライベートプロパティ
Vehicle.php— 「is-a(である)」関係を確立するベースとなるVehicleクラスを作成します。トレイトファイルをインクルードしてください。コンストラクタのプロパティ昇格(constructor promotion)を使用して、protectedな$brandプロパティを定義します。"Vehicle: " . $this->brandを返すメソッドdescribe()を追加します。このクラスは、フリート内のすべての車両がGPSを持つため、Trackableトレイトを使用する必要があります。Car.php—Vehicleを継承するCarクラスを作成します。Vehicleファイルをインクルードしてください。コンストラクタのプロパティ昇格を使用して、ブランド名とプライベートな$doors(integer) プロパティを受け取ります。ブランド名を使用して親クラスのコンストラクタを呼び出します。describe()メソッドをオーバーライドして、"Car: " . $this->brand . " with " . $this->doors . " doors"を返すようにします。main.php— Carファイルをインクルードします。ブランド名、ドアの数、場所の文字列の3つの入力を受け取ります。Carインスタンスを作成し、その場所を設定してから、次の2行を出力します:describe()を呼び出した結果getLocation()を呼び出した結果
ここで継承とトレイトがどのように連携しているかに注目してください。Car は Vehicle であり(継承がアイデンティティを確立)、Vehicle は追跡機能を「持っています」(トレイトが振る舞いを追加)。instanceof Vehicle を使用して何かが車両であるかどうかを確認できますが、確認対象となる「Trackable」型は存在しません。これがこれら2つのアプローチの主な違いです。
チートシート
「is-a」関係には継承(inheritance)を、「has-a」の能力にはトレイト(traits)を使用します。
継承はアイデンティティを確立します。子クラスは親クラスの一種(is a)であり、親の型が期待される場所ならどこでも使用できます。
トレイトはクラスのアイデンティティを変えることなく振る舞いを追加します。クラスはその機能を持って(has)いますが、そのものになるわけではありません。
<?php
// 継承: DogはAnimalである (IS an Animal)
class Animal {
public function breathe(): void {
echo "Breathing";
}
}
class Dog extends Animal {
public function bark(): void {
echo "Woof!";
}
}
// トレイト: Catはロギング機能を持っている (HAS logging capability)
trait Loggable {
public function log(string $msg): void {
echo "Log: $msg";
}
}
class Cat extends Animal {
use Loggable;
}
| 項目 | 継承 | トレイト |
|---|---|---|
| 関係 | is-a | has-a 能力 |
| 制限 | 単一の親のみ | 複数のトレイトを使用可能 |
| 型チェック | instanceof で動作する | 型のアイデンティティを持たない |
| 最適な用途 | 階層的な関係 | 関連のないクラス間での共有される振る舞い |
クラスが共通のアイデンティティを共有する階層的な関係には継承を使用します。関連のないクラス間で共有される振る舞いにはトレイトを使用します。
自分で試してみよう
<?php
// メインファイル - Carインスタンスの作成とテスト
// TODO: Car.phpファイルをインクルードする
// 入力を読み込む
$brand = trim(fgets(STDIN));
$doors = intval(trim(fgets(STDIN)));
$location = trim(fgets(STDIN));
// TODO: ブランドとドア数でCarインスタンスを作成する
// TODO: 車の場所を設定する
// TODO: describe()の結果を出力する
// TODO: getLocation()の結果を出力する
?>このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。