触って学べる図解:extendsとTraitの違いをロボット工場で例えてみた
はじめに
こんにちは!
オブジェクト指向を勉強していると、必ず出てくる extends(継承) と Trait(トレイト)。
「なんとなくわかるけど、どう使い分けるの?」って思いませんか?
今回は、ロボット工場に例えながら、クリックして遊べるインタラクティブな図解で、この2つの違いをざっくり理解してみましょう!
まずは触ってみよう!
百聞は一見にしかず。まずは下のカードをクリックしてみてください👇
extends(継承)とは?
extends は、親クラスの設計図を丸ごと引き継いで、新しいクラスを作る仕組みです。
class Robot {
public function move(): void
{
echo "歩く";
}
}
// BasicRobotはRobotの機能を全部引き継ぐ
class BasicRobot extends Robot {
public function greet(): void
{
echo "こんにちは!";
}
}
$robot = new BasicRobot();
$robot->move(); // 親から引き継いだ "歩く"
$robot->greet(); // 自分の "こんにちは!"
extendsのポイント
- ✅ 親の機能を丸ごと引き継げる
- ✅ コードの再利用がしやすい
- ❌ 親は1つだけ(PHPは単一継承)
- ❌ 「車」と「ロボット」を同時に親にできない
Trait(トレイト)とは?
Trait は、クラスに特定の機能だけをあとから追加できる仕組みです。
まるでロボットにオプションパーツを後付けするイメージです!
// 「飛ぶ」機能のTrait
trait CanFly {
public function fly(): void
{
echo "飛んだ!";
}
}
// 「炎を出す」機能のTrait
trait CanFlame {
public function flame(): void
{
echo "炎!";
}
}
// 複数のTraitを同時に使える!
class SuperRobot {
use CanFly, CanFlame;
}
$robot = new SuperRobot();
$robot->fly(); // "飛んだ!"
$robot->flame(); // "炎!"
Traitのポイント
- ✅ 複数のTraitを同時に使える
- ✅ 欲しい機能だけをポン付けできる
- ✅ 異なるクラスに同じ機能を共有できる
- ❌ 単体でインスタンス化はできない(あくまで「パーツ」)
まとめ:使い分けの基準
extends |
Trait |
|
|---|---|---|
| イメージ | 設計図の丸コピー | オプションパーツの後付け |
| 使える数 | 1つだけ | いくつでも |
| 向いている場面 | 「〇〇の一種」という関係 | 「〇〇できる」という機能追加 |
| 例 | Dog extends Animal |
use CanSwim, CanRun |

Traitをもっと活かす使い方
さっきの例を振り返ってみましょう。
trait CanFly {
public function fly(): void
{
echo "飛んだ!";
}
}
「飛んだ!」と表示するだけで、どのロボットが飛んでも同じ結果——こういう処理は、staticクラスでも書けますよね。
staticメソッドとは、new でオブジェクトを組み立てなくても クラス名::メソッド名() の形で直接呼び出せるメソッドのことです(new でオブジェクトを作ることを「インスタンス化」と呼びます)。
// staticクラスで書けるケース
class FlyHelper {
public static function fly(): void
{
echo "飛んだ!";
}
}
FlyHelper::fly(); // newしなくても呼べる
staticクラスはシンプルでテストもしやすいので、うちのプロジェクトでは「$this(自分自身)を使わないTraitは作らない。staticメソッドを持ったクラスを利用する」という方針をとっています。
では、Traitが本当に活きるのはどんなときかというと、インターフェースとセットで使うときだと思っています。
こちらも図解で触って確認してみましょう👇
インターフェース+Traitの組み合わせ
インターフェースとは、「このクラスは必ずこのメソッドを持つ」という約束書のようなものです。サインしたクラスは必ずその約束を守らなければなりません。
ロボット工場では、こんな2種類の約束書があるとします。
約束書①「型番管理できる」(Modelable)
・getModel() で自分の型番を返せる
約束書②「飛べる」(Flyable)
・fly() で飛べる
// 約束書①:型番を持つ
interface Modelable {
public function getModel(): string;
}
// 約束書②:飛べる
interface Flyable {
public function fly(): void;
}
ガンダム型ロボットと偵察ドローンは、両方の約束書にサインします。
class FlyingRobot implements Modelable, Flyable {
// getModel() と fly() を必ず実装しないといけない
}
class FlyingDrone implements Modelable, Flyable {
// こちらも同様
}
ここで考えてみましょう。getModel() の中身ってどのクラスでもほぼ同じですよね。
// FlyingRobotのgetModel
public function getModel(): string
{
return $this->modelId; // 自分のmodelIdを返すだけ
}
// FlyingDroneのgetModel
public function getModel(): string
{
return $this->modelId; // ここも全く同じ!
}
毎回同じコードを書くのは無駄です。でも fly() はロボットとドローンで飛び方が異なるため、各クラスで個別に実装するのは自然なことです。
そこでTraitの出番です。「どのクラスでも実装が同じになる部分」だけをTraitで共通化します。
// 約束書①:型番を持つ
interface Modelable {
public function getModel(): string;
}
// 約束書②:飛べる
interface Flyable {
public function fly(): void;
}
// Trait:getModel()のデフォルト実装を提供
// どのクラスでも「return $this->modelId」と書くだけなので共通化できる
trait HasModel {
public function getModel(): string
{
return $this->modelId; // $thisで自分自身のmodelIdにアクセス
}
}
// ガンダム型ロボット
class FlyingRobot implements Modelable, Flyable {
use HasModel; // getModel()はTraitにお任せ!
public function __construct(private string $modelId) {}
// fly()はロボット独自の実装(ブースターで飛ぶ)
public function fly(): void
{
echo $this->getModel() . "がブースターで飛んだ!";
}
}
// 偵察ドローン
class FlyingDrone implements Modelable, Flyable {
use HasModel; // getModel()は同じTraitにお任せ!
public function __construct(private string $modelId) {}
// fly()はドローン独自の実装(プロペラで飛ぶ)
public function fly(): void
{
echo $this->getModel() . "がプロペラで飛んだ!";
}
}
$gundam = new FlyingRobot("ガンダム型");
$drone = new FlyingDrone("偵察ドローン型");
$gundam->fly(); // "ガンダム型がブースターで飛んだ!"
$drone->fly(); // "偵察ドローン型がプロペラで飛んだ!"
$gundam->getModel(); // "ガンダム型" ← Traitが提供
$drone->getModel(); // "偵察ドローン型" ← 同じTraitが提供
ポイントは fly() と getModel() の役割の違いです。
-
fly():ロボットとドローンで飛び方が違う → 各クラスで個別に実装 -
getModel():どのクラスでもほぼ同じ実装になる → Traitで共通化
$this->modelId という形で自分自身のデータにアクセスできるので、同じTraitを使っても「ガンダム型」「偵察ドローン型」とそれぞれの型番を返せます。
おわりに
extends と Trait はどちらも「コードを再利用する仕組み」ですが、目的が違います。
-
extendsは親子関係を表す -
Traitは機能の追加に使う
この違いを意識するだけで、クラス設計がぐっとスッキリしますよ!
最初のインタラクティブ図解でも確認できるので、ぜひ何度でも触って感覚をつかんでみてください🤖
Discussion