インターフェースによるポリモーフィズム
CoddyのPHPジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 34/91。
前のレッスンでは、子クラスが親クラスのメソッドをオーバーライドする、継承によるポリモーフィズムについて学びました。インターフェースは、特に共通の振る舞いを持つ関連のないクラスを扱う場合に、ポリモーフィズムを実現するためのもう一つの強力な方法を提供します。
インターフェースを使用すると、ポリモーフィズムは契約そのものから生じます。インターフェースを実装するクラスは、その継承階層に関係なく、特定のメソッドを持つことが保証されます。
<?php
interface Notifiable {
public function send(string $message);
}
class EmailNotifier implements Notifiable {
public function send(string $message) {
return "Email: " . $message;
}
}
class SMSNotifier implements Notifiable {
public function send(string $message) {
return "SMS: " . $message;
}
}
class PushNotifier implements Notifiable {
public function send(string $message) {
return "Push: " . $message;
}
}
function notify(Notifiable $notifier, string $message) {
return $notifier->send($message);
}
echo notify(new EmailNotifier(), "Hello") . "\n";
echo notify(new SMSNotifier(), "Hello") . "\n";
echo notify(new PushNotifier(), "Hello");
出力:
Email: Hello
SMS: Hello
Push: Hellonotify() 関数は、任意の Notifiable オブジェクトで動作します。これらのクラスは親クラスを共有していません。同じインターフェースを実装している点を除けば、完全に無関係です。これが継承ベースのポリモーフィズムに対する主な利点です。家系(継承関係)ではなく、その能力によって無関係なクラスをグループ化できるのです。
重要なポイント: インターフェースベースのポリモーフィズムにより、共有された振る舞いに基づいて無関係なクラスを互換的に使用できるようになり、コードの柔軟性が向上し、特定の構成から切り離された(疎結合な)状態になります。
チャレンジ
簡単インターフェースに基づいたポリモーフィズムを実証するストレージシステムを構築しましょう。ファイルシステムとデータベースという、実装は全く異なりますが、同じストレージインターフェースを実装しているという共通点を持つ、異なるストレージバックエンドを作成します。単一の関数がどのストレージタイプでも動作するようにし、インターフェースが全く無関係なクラス間でどのようにポリモーフィズムを可能にするかを示します。
コードは4つのファイルに分けて構成します:
Storable.php—save($key, $data)とretrieve($key)という2つのメソッドシグネチャを持つStorableインターフェースを定義します。この規約により、内部でどのように実装されているかに関わらず、あらゆるストレージシステムがデータを保存および取得できることが保証されます。FileStorage.php—Storableを実装するFileStorageクラスを作成します。インターフェースファイルをインクルードしてください。このクラスは、コンストラクタを通じて設定されるプライベートな$directoryプロパティを持つ必要があります。save($key, $data)を実装し、"Saving '[data]' to file [directory]/[key].txt"を返すようにします。retrieve($key)を実装し、"Reading from file [directory]/[key].txt"を返すようにします。DatabaseStorage.php— 同じくStorableを実装するDatabaseStorageクラスを作成します。インターフェースファイルをインクルードしてください。このクラスは、コンストラクタを通じて設定されるプライベートな$tableNameプロパティを持つ必要があります。save($key, $data)を実装し、"Inserting '[data]' into table [tableName] with key [key]"を返すようにします。retrieve($key)を実装し、"Selecting from table [tableName] where key = [key]"を返すようにします。main.php— 両方のストレージファイルをインクルードします。Storableパラメータ、キー、およびデータを受け取るstoreDataという関数を作成します。この関数は、キーとデータを使用してsave()を呼び出した結果を返す必要があります。また、Storableパラメータとキーを受け取り、retrieve()を呼び出した結果を返すfetchDataという別の関数を作成します。
ディレクトリパス、テーブル名、キー、および保存するデータの4つの入力を受け取ります。ディレクトリを指定して FileStorage を、テーブル名を指定して DatabaseStorage を作成します。storeData() 関数を使用して、最初にファイルストレージ、次にデータベースストレージを使用してデータを保存します。各結果をそれぞれの行に出力します。次に、fetchData() を使用してファイルストレージからデータを取得し、その結果を出力します。
storeData() と fetchData() が両方のストレージタイプでどのように同じように動作するかに注目してください。これらの関数は、オブジェクトが Storable を実装していることだけを気にします。ファイルシステムとデータベースは内部実装が完全に異なりますが、インターフェースの規約を通じて交換可能になっています。
チートシート
インターフェースは、継承階層ではなく共有された振る舞いに基づいて、関連のないクラスを互換的に使用できるようにすることで、ポリモーフィズムを可能にします。
インターフェースを実装するクラスは、特定のメソッドを持つことを保証し、ポリモーフィックな振る舞いを可能にする契約を作成します:
<?php
interface Notifiable {
public function send(string $message);
}
class EmailNotifier implements Notifiable {
public function send(string $message) {
return "Email: " . $message;
}
}
class SMSNotifier implements Notifiable {
public function send(string $message) {
return "SMS: " . $message;
}
}
function notify(Notifiable $notifier, string $message) {
return $notifier->send($message);
}
echo notify(new EmailNotifier(), "Hello");
echo notify(new SMSNotifier(), "Hello");notify() 関数は、クラス階層に関係なく、Notifiable を実装する任意のオブジェクトを受け入れます。これにより、コードの柔軟性が高まり、特定の実装から切り離されます。
主な利点: インターフェースベースのポリモーフィズムは、関連のないクラスを家系ではなく機能によってグループ化し、共通の契約を通じて、まったく異なる実装を互換的に使用できるようにします。
自分で試してみよう
<?php
require_once 'FileStorage.php';
require_once 'DatabaseStorage.php';
// 入力を読み込む
$directory = trim(fgets(STDIN));
$tableName = trim(fgets(STDIN));
$key = trim(fgets(STDIN));
$data = trim(fgets(STDIN));
// TODO: Storable、key、dataを受け取る関数storeDataを作成する
// keyとdataを使用してsave()を呼び出した結果を返す必要がある
// TODO: Storableとkeyを受け取る関数fetchDataを作成する
// keyを使用してretrieve()を呼び出した結果を返す必要がある
// TODO: ディレクトリを指定してFileStorageインスタンスを作成する
// TODO: テーブル名を指定してDatabaseStorageインスタンスを作成する
// TODO: storeData()を使用してファイルストレージにデータを保存し、結果を表示する
// TODO: storeData()を使用してデータベースストレージにデータを保存し、結果を表示する
// TODO: fetchData()を使用してファイルストレージからデータを取得し、結果を表示する
?>このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。