インターフェース vs 抽象クラス
CoddyのPHPジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 30/91。
インターフェースと抽象クラスの両方を学んだところで、それぞれをいつ使うべきかを明確にしましょう。これらは似ているように見えるかもしれませんが、目的が異なり、それぞれ独自の機能を持っています。
| 機能 | インターフェース | 抽象クラス |
|---|---|---|
| メソッド | メソッドのシグネチャのみ | 抽象メソッドと具象メソッドの両方 |
| プロパティ | 定数のみ | プロパティを持つことができる |
| 多重継承 | クラスは複数を実装できる | クラスは1つだけ継承できる |
| コンストラクタ | __construct のシグネチャを宣言できる(本体なし) | 本体を持つコンストラクタを定義できる |
関連のないクラスが満たすことができる契約を定義したい場合は、インターフェースを使用します。例えば、PrinterとLoggerは共通の祖先を持っていなくても、両方ともWritableインターフェースを実装する場合があります。
共通のコードを共有する関連クラスがある場合は、抽象クラスを使用します。Dog クラスと Cat クラスの両方が同じ eat() メソッドの実装を必要とする場合、抽象クラス Animal を使用することで、そのコードを一度書くだけで済みます。
両方のアプローチを組み合わせることもできます。クラスは、複数のインターフェースを実装しながら、1つの抽象クラスを継承することができます:
<?php
abstract class Animal {
protected $name;
public function eat() {
return $this->name . " is eating";
}
}
interface Swimmable {
public function swim();
}
class Duck extends Animal implements Swimmable {
public function __construct($name) {
$this->name = $name;
}
public function swim() {
return $this->name . " is swimming";
}
}
重要なポイント: 関連のないクラス間で機能を定義する場合は、インターフェースを選択してください。関連するクラス間でコードを共有する必要がある場合は、抽象クラスを選択してください。
チャレンジ
簡単抽象クラスとインターフェースをいつ使い分けるか、そして両方のアプローチを効果的に組み合わせる方法を示す車両システムを構築しましょう。
継承を通じて共通のプロパティを共有しつつ、インターフェースを通じて追加の機能を得ることができるシステムを作成します。これは現実世界の設計上の決定を反映しています。例えば、車は車両の「一種である(is-a)」(継承)ですが、同時に燃料を補給できるという「機能を持っている(has the capability)」(インターフェース)でもあります。
コードを4つのファイルに分けて整理してください:
Refuelable.php—refuel($amount)という単一のメソッドシグネチャを持つRefuelableインターフェースを定義します。この機能は、継承階層に関係なく、燃料補給が必要なあらゆるクラスに適用できます。Vehicle.php— すべての車両に共通の機能を提供する抽象クラスVehicleを定義します。protected な$brandプロパティと、protected な$fuelLevelプロパティ(初期値は 0)を持つ必要があります。コンストラクタはブランド名を受け取ります。ブランドを返す具象メソッドgetBrand()、現在の燃料レベルを返す具象メソッドgetFuelLevel()、および子クラスが実装しなければならない抽象メソッドdrive()を含めてください。Car.php—Vehicleを継承し、Refuelableを実装するCarクラスを作成します。冒頭で必要な2つのファイルをインクルードしてください。コンストラクタはブランドを受け取り、それを親のコンストラクタに渡します。drive()を実装して"[brand] car is driving"を返すようにします。refuel($amount)を実装して、燃料レベルにその量を追加し、"[brand] refueled to [fuelLevel] liters"を返すようにします。main.php— Car ファイルをインクルードします。ブランド名と燃料の量(整数に変換)の2つの入力を受け取ります。そのブランドでCarインスタンスを作成します。1行目にdrive()の結果を出力し、2行目にその燃料の量でrefuel()を実行した結果を出力してください。
このチャレンジは、重要な違いを説明しています。抽象クラス Vehicle はコードを共有し「is-a」関係を確立しますが、Refuelable インターフェースは、車両である必要のない他の無関係なクラス(発電機やボートなど)にも適用できる機能を追加します。
自分で試してみよう
<?php
require_once 'Car.php';
// 入力を読み込む
$brand = trim(fgets(STDIN));
$fuelAmount = intval(trim(fgets(STDIN)));
// TODO: brand を使用して Car を作成する
// TODO: 1行目に drive() の結果を出力する
// TODO: 2行目に fuelAmount を指定して refuel() を実行した結果を出力する
?>このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。