PHPでのクラス継承の実装方法と「extends」キーワードの使い方を解説

PHPにおけるクラス継承は、オブジェクト指向プログラミング(OOP)の重要な概念の一つです。クラス継承を使用することで、既存のクラスの機能を再利用しつつ、新しい機能を追加したり、カスタマイズしたりすることが可能になります。PHPでは、この継承を実装するために「extends」キーワードを使用します。本記事では、クラス継承の基礎から、実際の使用方法や応用例までを解説し、OOPにおける効率的なコード設計を学ぶための手助けをします。PHPでのクラス継承を理解することで、コードの再利用性と保守性を向上させる方法を学びましょう。

目次

クラス継承とは


クラス継承とは、オブジェクト指向プログラミングにおける主要な機能の一つで、あるクラス(親クラスまたは基底クラス)の特性や機能を他のクラス(子クラスまたは派生クラス)が受け継ぐことを指します。これにより、既存のコードを再利用しつつ、新しい機能を追加できるため、効率的なコードの設計が可能です。

クラス継承の意義


クラス継承を利用することで、以下のようなメリットがあります。

  1. コードの再利用性向上:一度作成したクラスの機能を、他のクラスで再利用することで、冗長なコードを書く必要がなくなります。
  2. メンテナンスの容易さ:親クラスの修正が子クラスにも影響するため、コード全体の修正が一箇所で済み、保守が簡単になります。
  3. 拡張性の向上:既存のクラスを拡張して新たな機能を追加することで、柔軟なプログラム設計が可能となります。

PHPにおけるクラス継承の重要性


PHPでもクラス継承は非常に重要で、特に大規模なアプリケーション開発において、効率的に機能を再利用するための基盤となります。親クラスで定義したメソッドやプロパティを子クラスで引き継ぐことにより、オブジェクト指向の特徴であるカプセル化や抽象化が実現されます。

extendsキーワードの基本的な使い方


PHPでクラス継承を実現する際には、「extends」キーワードを使用します。子クラスが親クラスのプロパティやメソッドを継承することで、親クラスの機能を再利用し、必要に応じて新しい機能を追加できます。これにより、効率的かつシンプルなコードの設計が可能になります。

基本的な構文


PHPでクラス継承を使用する基本的な構文は以下の通りです。

class ParentClass {
    public function greet() {
        echo "Hello from ParentClass!";
    }
}

class ChildClass extends ParentClass {
    public function greetChild() {
        echo "Hello from ChildClass!";
    }
}

// インスタンス生成
$child = new ChildClass();
$child->greet(); // 親クラスのメソッドを呼び出し
$child->greetChild(); // 子クラスのメソッドを呼び出し

構文の解説

  • ParentClassは親クラス(基底クラス)で、greet()メソッドを持っています。
  • ChildClassは親クラスを継承しており、extendsキーワードを使って親クラスを指定します。
  • 子クラスChildClassは親クラスのgreet()メソッドをそのまま利用できるほか、新たにgreetChild()メソッドを追加しています。

このように、extendsを使用することで、親クラスの機能を引き継ぎつつ、必要に応じて子クラスに独自の機能を追加できるのが継承の強みです。

基底クラスと派生クラスの違い


クラス継承において、親クラスは「基底クラス」、子クラスは「派生クラス」と呼ばれます。基底クラスは、共通の機能やデータを定義し、派生クラスはその機能を継承しつつ、独自の振る舞いを追加することができます。この違いを理解することは、継承を活用する際に非常に重要です。

基底クラス(親クラス)とは


基底クラスは、共通のプロパティやメソッドを定義するクラスです。以下は基底クラスの例です。

class Animal {
    public $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function makeSound() {
        echo $this->name . " is making a sound.";
    }
}

このクラスは、動物という広い概念を表現しており、動物全般に共通する特性(名前)や行動(音を出す)を定義しています。

派生クラス(子クラス)とは


派生クラスは、基底クラスを拡張し、より具体的な機能や特性を追加したクラスです。以下は、Animalクラスを継承した派生クラスの例です。

class Dog extends Animal {
    public function makeSound() {
        echo $this->name . " says: Woof!";
    }
}

このDogクラスは、Animalクラスを継承しつつ、makeSound()メソッドを上書きして、犬特有の音を出す動作を実現しています。

基底クラスと派生クラスの役割の違い

  • 基底クラス:共通のプロパティやメソッドを持つ、汎用的なクラス。
  • 派生クラス:基底クラスを継承し、特定の機能や振る舞いを追加または上書きしたクラス。

派生クラスは基底クラスの機能をそのまま利用できるため、コードの再利用性が高まります。また、派生クラスで新たな機能を追加することで、特定の用途に合わせたクラスを構築することが可能です。

メソッドオーバーライドの仕組み


PHPにおけるメソッドオーバーライドとは、子クラスが親クラスで定義されたメソッドを上書きして独自の処理を提供することです。オーバーライドを使用することで、親クラスの基本的な機能を引き継ぎつつ、特定の振る舞いを変更することが可能になります。

メソッドオーバーライドの基本


メソッドオーバーライドでは、子クラスに同じ名前のメソッドを定義し、その中で異なる処理を実装します。以下の例では、親クラスのメソッドが子クラスでオーバーライドされています。

class ParentClass {
    public function greet() {
        echo "Hello from ParentClass!";
    }
}

class ChildClass extends ParentClass {
    public function greet() {
        echo "Hello from ChildClass!";
    }
}

$child = new ChildClass();
$child->greet();  // 結果: Hello from ChildClass!

この例では、親クラスのgreet()メソッドは「Hello from ParentClass!」と出力しますが、子クラスではそのメソッドをオーバーライドし、「Hello from ChildClass!」というメッセージを表示するように変更されています。

親クラスのメソッドを利用したオーバーライド


メソッドオーバーライド時に、親クラスのメソッドをそのまま使用したい場合は、parent::を使って呼び出すことができます。これにより、親クラスの処理を保持しながら、追加の処理を行うことができます。

class Animal {
    public function makeSound() {
        echo "Some generic animal sound.";
    }
}

class Dog extends Animal {
    public function makeSound() {
        parent::makeSound();  // 親クラスのメソッドを呼び出し
        echo " Woof!";
    }
}

$dog = new Dog();
$dog->makeSound();  // 結果: Some generic animal sound. Woof!

この例では、DogクラスのmakeSound()メソッドで、まず親クラスのメソッドを呼び出し、その後に犬特有の「Woof!」を追加しています。これにより、親クラスの基本機能を保持しつつ、子クラスでのカスタマイズが可能になります。

オーバーライドのメリット

  1. 柔軟性の向上:親クラスの基本的な動作を変更できるため、特定のシナリオに応じた振る舞いを実装可能です。
  2. コードの再利用:親クラスの機能を無駄にせず、新しい機能を追加したり、特定の挙動を上書きしたりできます。
  3. 保守性の向上:共通部分は親クラスに保持し、各派生クラスで個別の処理のみを記述できるため、メンテナンスが容易です。

コンストラクタの継承


PHPでは、クラスのインスタンスが生成されるときに呼び出されるメソッドが「コンストラクタ」です。クラス継承時には、親クラスのコンストラクタを子クラスで利用することが可能です。これにより、親クラスで初期化されたプロパティや処理を子クラスでも引き継ぎつつ、必要に応じて子クラスに特有の初期化処理を追加できます。

親クラスのコンストラクタを継承する基本的な方法


親クラスのコンストラクタを子クラスで呼び出すには、parent::__construct()を使用します。これにより、親クラスの初期化処理が子クラスにも適用されます。以下はその基本的な構文です。

class ParentClass {
    protected $name;

    public function __construct($name) {
        $this->name = $name;
        echo "Parent constructor: " . $this->name . "\n";
    }
}

class ChildClass extends ParentClass {
    private $age;

    public function __construct($name, $age) {
        parent::__construct($name);  // 親クラスのコンストラクタを呼び出し
        $this->age = $age;
        echo "Child constructor: Age is " . $this->age . "\n";
    }
}

$child = new ChildClass("John", 25);
// 結果:
// Parent constructor: John
// Child constructor: Age is 25

この例では、ParentClassのコンストラクタが子クラスChildClassのコンストラクタで呼び出され、親クラスでの初期化処理(名前の設定)が行われた後、子クラスで年齢の設定が行われます。

親クラスのプロパティを初期化する利点


親クラスで定義されたプロパティや初期化処理を子クラスで引き継ぐことで、以下の利点があります。

  • コードの重複を防ぐ:親クラスで定義されたプロパティや初期化処理を再度子クラスで記述する必要がなく、冗長なコードを回避できます。
  • 保守性が高まる:親クラスのコンストラクタで一箇所にまとめて初期化処理を記述できるため、変更が必要な場合も修正箇所が少なくなり、メンテナンスが容易です。

コンストラクタのオーバーライド


場合によっては、親クラスのコンストラクタを完全に上書き(オーバーライド)することも可能です。これは、親クラスの初期化処理を必要としない場合や、子クラス独自の初期化を行いたい場合に有効です。

class ParentClass {
    public function __construct() {
        echo "Parent constructor called\n";
    }
}

class ChildClass extends ParentClass {
    public function __construct() {
        echo "Child constructor called\n";
    }
}

$child = new ChildClass();
// 結果: Child constructor called

この場合、ChildClassのコンストラクタが完全に親クラスのコンストラクタを上書きし、ParentClassのコンストラクタは呼び出されません。

コンストラクタの継承やオーバーライドを使い分けることで、クラス間の初期化処理を柔軟に設計できます。

protected修飾子を使った継承


PHPでは、クラスのメンバ変数やメソッドに対してアクセス修飾子を設定することができます。その中でも「protected」修飾子は、クラスの外部からはアクセスできませんが、クラス自体やその子クラス(派生クラス)からはアクセス可能な特殊な権限を持っています。この「protected」修飾子は、クラス継承において、親クラスのプロパティやメソッドを子クラスで安全に利用したい場合に非常に有効です。

protected修飾子の役割


「protected」修飾子は、クラスの外部から直接アクセスされることを防ぎつつ、親クラスと子クラス間でデータを共有したい場合に使用されます。以下の例では、protected修飾子を使って、親クラスのプロパティを子クラスで利用しています。

class ParentClass {
    protected $name;

    public function __construct($name) {
        $this->name = $name;
    }

    protected function showName() {
        echo "Name: " . $this->name . "\n";
    }
}

class ChildClass extends ParentClass {
    public function display() {
        $this->showName();  // 親クラスのprotectedメソッドを呼び出し
    }
}

$child = new ChildClass("John");
$child->display();  // 結果: Name: John

この例では、$nameというプロパティとshowName()メソッドがprotectedとして定義されています。親クラスの外部からは直接アクセスできませんが、子クラスからはアクセスできるため、継承関係における安全なデータ共有が実現されています。

protectedのメリット


protected修飾子を使用することで、以下のようなメリットが得られます。

  • データの隠蔽:クラスの内部で管理したいプロパティやメソッドを外部から直接操作されないように保護しつつ、必要な場合に子クラスで利用できます。
  • 継承関係での柔軟性:親クラスの機能をそのまま子クラスで活用できるため、クラスの再利用性が高まります。また、private修飾子とは異なり、親クラスで定義されたプロパティやメソッドに子クラスからもアクセスできるため、カスタマイズや拡張が容易です。

privateとの違い


private修飾子との大きな違いは、privateは親クラスの内部でのみアクセス可能で、子クラスからはアクセスできない点です。以下はその違いを示す例です。

class ParentClass {
    private $secret = "This is a secret";

    private function showSecret() {
        echo $this->secret;
    }
}

class ChildClass extends ParentClass {
    public function reveal() {
        // $this->showSecret(); // エラー:親クラスのprivateメソッドにはアクセスできない
    }
}

$child = new ChildClass();
$child->reveal();  // エラーが発生

このように、privateの場合は子クラスからアクセスできませんが、protectedを使えば子クラスでも自由に利用できるため、継承を活用したクラス設計において、protectedはより柔軟なアクセス制御を提供します。

protected修飾子を正しく活用することで、データの安全性を確保しつつ、クラス間での再利用性を向上させることができます。

多重継承がサポートされない理由


PHPはオブジェクト指向言語ですが、他の多くのプログラミング言語と同様に「多重継承」をサポートしていません。多重継承とは、一つの子クラスが複数の親クラスから同時に継承することを指します。PHPでは、あるクラスは一つの親クラスのみを継承できるという単一継承のルールが存在します。これは、多重継承が持ついくつかの問題点を回避するためです。

多重継承がもたらす問題


多重継承には、一見便利に思える点もありますが、同時に以下のような問題を引き起こす可能性があります。

1. ダイヤモンド問題


多重継承を使うと、「ダイヤモンド問題」と呼ばれる複雑な問題が発生することがあります。ダイヤモンド問題とは、二つの親クラスがそれぞれ同じメソッドを持っていて、その両方を継承する子クラスがどのメソッドを優先して実行すべきかが曖昧になる状況を指します。

class A {
    public function hello() {
        echo "Hello from A";
    }
}

class B extends A {
    public function hello() {
        echo "Hello from B";
    }
}

class C extends A {
    public function hello() {
        echo "Hello from C";
    }
}

class D extends B, C { // PHPではサポートされない構文
    // どのhelloメソッドを継承すべきか?
}

この例では、クラスDBCの両方を継承する際に、hello()メソッドがどちらのクラスのものを使用すべきかが不明確です。このような競合が発生するため、PHPは多重継承を禁止しています。

2. 複雑さの増加


多重継承により、クラスの階層構造が複雑化し、デバッグやメンテナンスが難しくなる可能性があります。複数の親クラスから継承されるメソッドやプロパティが競合することで、意図しない動作が発生することも多く、コードの可読性や保守性に悪影響を及ぼします。

PHPにおける多重継承の代替手段


PHPは多重継承をサポートしていませんが、その代替として「インターフェース」や「トレイト」を利用することが推奨されています。これらの機能を使うことで、多重継承のような柔軟性を持ちつつ、複雑な問題を回避できます。

インターフェースの利用


インターフェースを使えば、クラスは複数のインターフェースを実装できます。これにより、共通のメソッドを定義しながら、具体的な実装は各クラスで行うことができます。

interface CanFly {
    public function fly();
}

interface CanSwim {
    public function swim();
}

class Bird implements CanFly {
    public function fly() {
        echo "Flying!";
    }
}

class Fish implements CanSwim {
    public function swim() {
        echo "Swimming!";
    }
}

トレイトの利用


PHPの「トレイト」は、多重継承に代わるもう一つの機能で、複数のクラスで使い回せるメソッドを提供することができます。トレイトは複数のクラスに共通の機能を実装する際に便利です。

trait Logger {
    public function log($message) {
        echo "Log: " . $message;
    }
}

class Application {
    use Logger;
}

class Database {
    use Logger;
}

$app = new Application();
$app->log("Application started.");

$db = new Database();
$db->log("Database connected.");

このように、PHPは多重継承の代わりに、トレイトやインターフェースを使ってクラス設計の柔軟性を確保しつつ、複雑さや競合問題を回避できるようになっています。

トレイトを使ったコードの再利用


PHPのトレイト(Trait)は、多重継承の代替として導入された強力な機能で、複数のクラス間でコードを再利用する際に非常に役立ちます。トレイトを使うことで、共通のメソッドを複数のクラスに簡単に追加でき、コードの冗長さを減らし、保守性を高めることができます。

トレイトの基本概念


トレイトは、クラスに似た構造を持っていますが、クラスではありません。そのため、トレイト自体からインスタンスを生成することはできません。代わりに、クラスがトレイトを「use」キーワードで取り込むことで、そのトレイト内のメソッドやプロパティを利用できます。

トレイトの基本的な使い方


以下に、トレイトを使った基本的なコード例を示します。

trait GreetingTrait {
    public function greet() {
        echo "Hello!";
    }
}

class Person {
    use GreetingTrait;
}

class Animal {
    use GreetingTrait;
}

$person = new Person();
$person->greet();  // 結果: Hello!

$animal = new Animal();
$animal->greet();  // 結果: Hello!

この例では、GreetingTraitというトレイトを定義し、それをPersonクラスとAnimalクラスで再利用しています。それぞれのクラスがgreet()メソッドを直接持っていないにもかかわらず、トレイトを使うことでそのメソッドを使用することができます。

トレイトのメリット


トレイトを使用することで、以下のようなメリットがあります。

1. コードの再利用性向上


複数のクラスで共通するメソッドやプロパティを定義する際、トレイトを利用することでコードの重複を防ぎます。クラスごとに同じメソッドを何度も書く必要がなくなり、保守性も向上します。

2. 柔軟なクラス設計


トレイトは、PHPで多重継承を実現する方法として非常に有効です。複数のトレイトを一つのクラスで取り込むことができるため、共通の機能を自由に組み合わせて柔軟にクラスを設計できます。

trait Logger {
    public function log($message) {
        echo "Log: " . $message;
    }
}

trait FileHandler {
    public function openFile($filename) {
        echo "Opening file: " . $filename;
    }
}

class Application {
    use Logger, FileHandler;
}

$app = new Application();
$app->log("Application started.");  // 結果: Log: Application started.
$app->openFile("test.txt");         // 結果: Opening file: test.txt

この例では、LoggerトレイトとFileHandlerトレイトをApplicationクラスで同時に使用しており、それぞれの機能を組み合わせて活用しています。

メソッドの競合とオーバーライド


複数のトレイトを同時に利用する場合、同じ名前のメソッドが存在すると競合が発生します。このような場合、子クラスでそのメソッドをオーバーライドすることで、競合を解決することができます。

trait TraitA {
    public function message() {
        echo "Message from TraitA";
    }
}

trait TraitB {
    public function message() {
        echo "Message from TraitB";
    }
}

class MyClass {
    use TraitA, TraitB {
        TraitB::message insteadof TraitA;  // TraitBのメソッドを優先
        TraitA::message as showMessageA;   // TraitAのメソッドも別名で使用
    }
}

$obj = new MyClass();
$obj->message();     // 結果: Message from TraitB
$obj->showMessageA(); // 結果: Message from TraitA

この例では、TraitATraitBの両方にmessage()メソッドが存在していますが、insteadofを使用してTraitBのメソッドを優先し、さらにTraitAのメソッドを別名showMessageA()として呼び出しています。

トレイトを使った実用例


トレイトは、たとえばログ管理やファイル操作など、アプリケーション全体で共有したい機能を提供する際に有効です。また、複数のクラスで共通して使用する補助的な機能やユーティリティコードをまとめて提供するのに適しています。

トレイトを活用することで、PHPでのクラス設計がより柔軟になり、コードの再利用性を高めることができます。特に、大規模なシステム開発において、トレイトは効率的なコーディングを可能にします。

クラス継承の実用例


クラス継承は、オブジェクト指向プログラミングの重要な要素であり、コードの再利用や拡張に大きく貢献します。ここでは、PHPにおけるクラス継承を使った具体的な実用例を紹介し、継承がどのように実際のアプリケーション設計に役立つかを見ていきます。

例1: ユーザー管理システム


ユーザー管理システムでは、一般ユーザーと管理者ユーザーの両方を扱うケースがよくあります。これをクラス継承を使って効率的に設計することができます。

class User {
    protected $name;
    protected $email;

    public function __construct($name, $email) {
        $this->name = $name;
        $this->email = $email;
    }

    public function getInfo() {
        return "Name: " . $this->name . ", Email: " . $this->email;
    }
}

class AdminUser extends User {
    private $permissions;

    public function __construct($name, $email, $permissions) {
        parent::__construct($name, $email);  // 親クラスのコンストラクタを呼び出す
        $this->permissions = $permissions;
    }

    public function getInfo() {
        return parent::getInfo() . ", Permissions: " . implode(", ", $this->permissions);
    }
}

// ユーザーのインスタンスを作成
$user = new User("John Doe", "john@example.com");
echo $user->getInfo();  // 結果: Name: John Doe, Email: john@example.com

// 管理者ユーザーのインスタンスを作成
$admin = new AdminUser("Admin", "admin@example.com", ["edit", "delete"]);
echo $admin->getInfo();  // 結果: Name: Admin, Email: admin@example.com, Permissions: edit, delete

この例では、Userクラスが一般ユーザーを表し、その機能を継承したAdminUserクラスが管理者ユーザーを表しています。AdminUserUserクラスの機能を再利用しつつ、管理者特有の権限($permissions)を追加しています。

例2: 支払いシステムのクラス継承


支払いシステムにおいて、異なる支払い方法(例:クレジットカード、銀行振込)を管理するために継承を利用することができます。

class Payment {
    protected $amount;

    public function __construct($amount) {
        $this->amount = $amount;
    }

    public function processPayment() {
        echo "Processing payment of $" . $this->amount;
    }
}

class CreditCardPayment extends Payment {
    private $cardNumber;

    public function __construct($amount, $cardNumber) {
        parent::__construct($amount);  // 親クラスのコンストラクタを呼び出す
        $this->cardNumber = $cardNumber;
    }

    public function processPayment() {
        echo "Processing credit card payment of $" . $this->amount . " using card " . $this->cardNumber;
    }
}

class BankTransferPayment extends Payment {
    private $bankAccount;

    public function __construct($amount, $bankAccount) {
        parent::__construct($amount);  // 親クラスのコンストラクタを呼び出す
        $this->bankAccount = $bankAccount;
    }

    public function processPayment() {
        echo "Processing bank transfer of $" . $this->amount . " to account " . $this->bankAccount;
    }
}

// クレジットカード支払い
$creditCardPayment = new CreditCardPayment(100, "1234-5678-9876");
$creditCardPayment->processPayment();  // 結果: Processing credit card payment of $100 using card 1234-5678-9876

// 銀行振込
$bankPayment = new BankTransferPayment(200, "BANK-12345");
$bankPayment->processPayment();  // 結果: Processing bank transfer of $200 to account BANK-12345

この例では、Paymentクラスが基本的な支払い機能を定義しており、それを継承してCreditCardPaymentBankTransferPaymentクラスがそれぞれの支払い方法に特化した実装を行っています。これにより、異なる支払い手段を一貫したインターフェースで扱うことができ、コードの再利用が促進されます。

例3: 製品とデジタル製品の継承


商品管理システムでは、一般的な製品とデジタル製品(例:電子書籍やソフトウェア)の両方を扱うことがあります。この場合もクラス継承を活用することで、共通の機能を持ちつつ、特定の機能を追加することができます。

class Product {
    protected $name;
    protected $price;

    public function __construct($name, $price) {
        $this->name = $name;
        $this->price = $price;
    }

    public function getInfo() {
        return $this->name . " costs $" . $this->price;
    }
}

class DigitalProduct extends Product {
    private $fileSize;

    public function __construct($name, $price, $fileSize) {
        parent::__construct($name, $price);  // 親クラスのコンストラクタを呼び出す
        $this->fileSize = $fileSize;
    }

    public function getInfo() {
        return parent::getInfo() . ", File size: " . $this->fileSize . "MB";
    }
}

// 一般的な製品
$product = new Product("Laptop", 1000);
echo $product->getInfo();  // 結果: Laptop costs $1000

// デジタル製品
$digitalProduct = new DigitalProduct("E-book", 20, 5);
echo $digitalProduct->getInfo();  // 結果: E-book costs $20, File size: 5MB

この例では、Productクラスが基本的な製品情報を提供し、それを継承したDigitalProductクラスがデジタル製品特有の属性であるfileSizeを追加しています。このように、継承を利用することで、クラス構造をシンプルかつ再利用可能に保つことができます。

クラス継承を使うことで、システム全体の保守性と拡張性を大幅に向上させることができます。異なるコンテキストに応じたカスタマイズが必要な場合でも、共通のコードベースを維持しながら効率的に機能を追加できます。

クラス継承を使った演習問題


クラス継承の理解を深めるために、以下の演習問題を通じて実際に手を動かしてみましょう。これらの問題は、PHPにおけるクラス継承、メソッドオーバーライド、トレイトの利用を応用する機会を提供します。

問題1: 車とバスのクラスを作成する


次の条件に従って、Vehicleクラスを作成し、それを継承したCarクラスとBusクラスを実装してください。

  1. Vehicleクラスには、namespeedというプロパティを持ち、move()メソッドで車が動いていることを表示します。
  2. CarクラスはVehicleクラスを継承し、passengersというプロパティを追加します。move()メソッドをオーバーライドし、乗客数を表示します。
  3. BusクラスもVehicleクラスを継承し、capacityというプロパティを追加し、バスの定員を表示します。
class Vehicle {
    protected $name;
    protected $speed;

    public function __construct($name, $speed) {
        $this->name = $name;
        $this->speed = $speed;
    }

    public function move() {
        echo $this->name . " is moving at " . $this->speed . " km/h.\n";
    }
}

class Car extends Vehicle {
    private $passengers;

    public function __construct($name, $speed, $passengers) {
        parent::__construct($name, $speed);
        $this->passengers = $passengers;
    }

    public function move() {
        echo $this->name . " is moving at " . $this->speed . " km/h with " . $this->passengers . " passengers.\n";
    }
}

class Bus extends Vehicle {
    private $capacity;

    public function __construct($name, $speed, $capacity) {
        parent::__construct($name, $speed);
        $this->capacity = $capacity;
    }

    public function move() {
        echo $this->name . " is moving at " . $this->speed . " km/h with a capacity of " . $this->capacity . " passengers.\n";
    }
}

// 実行例
$car = new Car("Car", 120, 4);
$car->move();  // 結果: Car is moving at 120 km/h with 4 passengers.

$bus = new Bus("Bus", 80, 50);
$bus->move();  // 結果: Bus is moving at 80 km/h with a capacity of 50 passengers.

問題2: トレイトを使ってログ機能を追加する


LoggerTraitというトレイトを作成し、そのトレイトを使って、CarクラスとBusクラスにログ機能を追加してください。

  1. LoggerTraitには、log()メソッドを作成し、任意のメッセージをログとして表示します。
  2. CarBusのクラスにLoggerTraitを追加し、それぞれのmove()メソッドでログを記録します。
trait LoggerTrait {
    public function log($message) {
        echo "Log: " . $message . "\n";
    }
}

class Vehicle {
    protected $name;
    protected $speed;

    public function __construct($name, $speed) {
        $this->name = $name;
        $this->speed = $speed;
    }

    public function move() {
        echo $this->name . " is moving at " . $this->speed . " km/h.\n";
    }
}

class Car extends Vehicle {
    use LoggerTrait;

    private $passengers;

    public function __construct($name, $speed, $passengers) {
        parent::__construct($name, $speed);
        $this->passengers = $passengers;
    }

    public function move() {
        $this->log($this->name . " is starting to move.");
        echo $this->name . " is moving at " . $this->speed . " km/h with " . $this->passengers . " passengers.\n";
    }
}

class Bus extends Vehicle {
    use LoggerTrait;

    private $capacity;

    public function __construct($name, $speed, $capacity) {
        parent::__construct($name, $speed);
        $this->capacity = $capacity;
    }

    public function move() {
        $this->log($this->name . " is starting to move.");
        echo $this->name . " is moving at " . $this->speed . " km/h with a capacity of " . $this->capacity . " passengers.\n";
    }
}

// 実行例
$car = new Car("Car", 120, 4);
$car->move();  // 結果: Log: Car is starting to move. Car is moving at 120 km/h with 4 passengers.

$bus = new Bus("Bus", 80, 50);
$bus->move();  // 結果: Log: Bus is starting to move. Bus is moving at 80 km/h with a capacity of 50 passengers.

これらの問題を通じて、クラス継承やトレイトの仕組み、オーバーライドの使い方を実際に体験し、理解を深めることができます。どちらの問題も実務的な場面で頻繁に使われるため、解答してみて、クラス継承の効果的な使い方をマスターしましょう。

まとめ


本記事では、PHPにおけるクラス継承の基本的な概念から、extendsキーワードを使った実装方法、メソッドオーバーライドやコンストラクタの継承、そしてprotected修飾子の役割まで幅広く解説しました。さらに、多重継承がサポートされない理由や、その代替手段としてのトレイトの利用方法についても説明しました。クラス継承は、効率的なコード再利用や保守性の向上に役立つ強力なツールです。今回紹介した実用例や演習問題を通して、実際に手を動かし、継承を活用した柔軟なクラス設計を習得してください。

コメント

コメントする

目次