インターフェースと抽象クラスの比較
CoddyのPHPジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 30/91。
インターフェースと抽象クラスの両方を学んだので、それぞれをいつ使うべきか明確にしましょう。似ているように見えるかもしれませんが、目的は異なり、できることにも違いがあります。
| 機能 | インターフェース | 抽象クラス |
|---|---|---|
| メソッド | method のシグネチャのみ | abstract および具体的なメソッド |
| プロパティ | 定数のみ | property を持てる |
| 多重継承 | class は複数を Implement できる | class は1つだけ extends できる |
| Constructor | __construct のシグネチャを declare できる(body なし) | body を持つ Constructor を定義できる |
互いに関係のないクラスが実装できる契約を定義したい場合は、interfaceを使用します。たとえば、PrinterとLoggerは共通の祖先を持たなくても、どちらもWritable interfaceを実装できます。
共通のコードを共有する関連クラスがある場合は、abstract classを使用します。DogクラスとCatクラスの両方で同じeat()メソッドの実装が必要な場合、abstractなAnimalクラスを使えば、そのコードを一度だけ記述できます。
両方のアプローチを組み合わせることもできます。クラスは抽象クラスを拡張しながら、複数のインターフェースを実装できます。
<?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";
}
}
重要なポイント: 関係のない class 全体で能力を定義するには interface を選びます。関連する class 間でコードを共有する必要がある場合は、abstract class を選びます。
チャレンジ
簡単abstract class と interface のどちらを使うべきか、また両方のアプローチを効果的に組み合わせる方法を示す vehicle system を構築しましょう。
継承を通じて vehicles が共通の properties を共有し、さらに interfaces を通じて追加の capabilities を獲得できる system を作成します。これは現実世界の設計上の判断を反映しています。car は "is a" vehicle(継承)ですが、同時に refuelable である "has the capability"(interface)も備えています。
コードを4つのファイルに分けて整理します。
Refuelable.php:単一の method signaturerefuel($amount)を持つRefuelableinterface を Define します。この capability は、継承階層に関係なく、refueling が必要なあらゆる class に適用できます。Vehicle.php:すべての vehicles に共通する functionality を提供する abstractVehicleclass を Define します。protected$brandproperty と、0 から始まる protected$fuelLevelproperty を持たせます。constructor は brand name を accepts します。brand を返す concretegetBrand()method、current fuel level を返す concretegetFuelLevel()method、そして child classes が Implement する必要のある abstractdrive()method を含めます。Car.php:Vehicleを extends し、Refuelableを implements するCarclass を Create します。先頭で required files の両方を Include します。constructor は brand を accepts し、それを parent constructor に passes します。drive()を Implement して"[brand] car is driving"を返します。refuel($amount)を Implement して、amount を fuel level に Add し、"[brand] refueled to [fuelLevel] liters"を返します。main.php:Car file を Include します。2つの inputs、つまり brand name と fuel amount(integer に convert)を受け取ります。brand を使ってCarを Create します。最初の line にdrive()の結果を Print し、次に2行目に fuel amount を使ったrefuel()の結果を Print します。
この challenge は重要な違いを示しています。abstract Vehicle class は code を共有し、"is-a" relationship を確立します。一方、Refuelable interface は、generator や boat のような、vehicle とは無関係な他の classes にも、それらが vehicles である必要なく適用できる capability を追加します。
自分で試してみよう
<?php
require_once 'Car.php';
// 入力を読み取る
$brand = trim(fgets(STDIN));
$fuelAmount = intval(trim(fgets(STDIN)));
// TODO: ブランドでCarを作成する
// TODO: 1行目にdrive()の結果を出力する
// TODO: 2行目に燃料量でrefuel()の結果を出力する
?>このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
自分で練習してみよう: PHPオンラインコンパイラ