[PHP]interfaceについてのメモ
大まかに
interface を使用するメリットは、
- 制約
- 拡張性
- 代替可能性
制約
interface を委譲しているということは、 必要となるメソッドが宣言されているということ。
そして、そのメソッドは in と out が決まっているということ。
現実社会においても、「なんかいい感じの資料がほしい」となった時に、「なんかいい感じの資料」を返してくれる人に仕事を依頼するはず。
interface なんかいい感じの仕事
{
public function なんかいい感じの資料を返す(): なんかいい感じの資料;
}
class 社員A implements なんかいい感じの仕事
{
public function なんかいい感じの資料を返す(): なんかいい感じの資料
{
return new なんかいい感じの資料();
}
}
社員Aを使用する時点で、なんかいい感じの資料を返す()が実装されていることが決まっているので、安心して仕事を依頼することができる。
拡張性
オープンクローズドの原則は、「ソフトウェアに新しく機能を追加するとき、既存のコードを変更せず新しいコードを追加するだけで済むようにしておくべきであるという意味」で、これを実現することができる。
interfaceを使用しないパターン
function いつものください(string お客さん名): 食べ物
{
if (お客さん名 === 田中さん) {
return new 食べ物('ラーメン');
}
if (お客さん名 === 佐藤さん) {
return new 食べ物('炒飯');
}
throw new どちらさまですかException();
}
新しい常連鈴木さんが現れた時に if の追加が行われる。無限に続く。
もちろん、 if の部分をマッピングした配列に代替することは可能だけれど、別の条件なんて追加された時には即破綻する。
function いつものください(string お客さん名): 食べ物
{
if (お客さん名 === 田中さん) {
return new 食べ物('ラーメン');
}
if (お客さん名 === 佐藤さん) {
return new 食べ物('炒飯');
}
if (お客さん名 === 鈴木さん) {
if (晴れ) {
return new 食べ物('唐揚げ');
} else {
return new 食べ物('野菜炒め');
}
}
throw new どちらさまですかException();
}
interfaceを使用するパターン
以下は、インターフェースを使用して常連客の注文を処理する例です。この設計により、新しい常連客を追加する際に既存のロジックを変更する必要がなくなります。
まず、常連客のインターフェースを定義します。
<?php
interface 常連
{
public function いつものください(): 食べ物;
}
次に、具体的な常連客クラスを実装します。
<?php
class 常連_田中さん implements 常連
{
public function いつものください(): 食べ物
{
return new 食べ物('ラーメン');
}
}
class 常連_佐藤さん implements 常連
{
public function いつものください(): 食べ物
{
return new 食べ物('炒飯');
}
}
新しい常連客を追加する際には、対応するクラスを追加するだけで済みます。例えば、鈴木さんを追加する場合は以下のように実装します。
<?php
class 常連_鈴木さん implements 常連
{
private $天気;
public function __construct(bool $晴れ)
{
$this->天気 = $晴れ;
}
public function いつものください(): 食べ物
{
if ($this->天気) {
return new 食べ物('唐揚げ');
} else {
return new 食べ物('野菜炒め');
}
}
}
次に、常連客のインスタンスを生成するファクトリークラスを実装します。
<?php
class 常連Factory
{
public static function factory(string $お客さん名, bool $晴れ = true): 常連
{
$className = '常連_' . $お客さん名;
if (class_exists($className)) {
if ($className === '常連_鈴木さん') {
return new $className($晴れ);
}
return new $className();
}
throw new Exception('どちらさまですか');
}
}
最後に、注文を処理するお店のクラスを実装します。
<?php
class お店
{
public function 注文聞く(常連 $常連): 食べ物
{
return $常連->いつものください();
}
}
これで、新しい常連客を追加する際には、対応するクラスを追加するだけで済みます。既存のロジックを変更する必要はありません。
使用例:
<?php
$お店 = new お店();
try {
$常連 = 常連Factory::factory('田中さん');
echo $お店->注文聞く($常連)->名前; // ラーメン
$常連 = 常連Factory::factory('佐藤さん');
echo $お店->注文聞く($常連)->名前; // 炒飯
$常連 = 常連Factory::factory('鈴木さん', true);
echo $お店->注文聞く($常連)->名前; // 唐揚げ
$常連 = 常連Factory::factory('鈴木さん', false);
echo $お店->注文聞く($常連)->名前; // 野菜炒め
} catch (Exception $e) {
echo $e->getMessage();
}
この設計により、新しい常連客を追加する際に既存のコードを変更する必要がなくなり、オープンクローズドの原則に従った拡張が可能になります。
代替可能性
制約の内容で記述した内容が、人の目線ではなく、プログラムの目線だと in と out が決まると代替が可能と解釈できる。
interface なんかいい感じの仕事
{
public function なんかいい感じの資料を返す(): なんかいい感じの資料;
}
class 社員A implements なんかいい感じの仕事
{
public function なんかいい感じの資料を返す(): なんかいい感じの資料
{
return new なんかいい感じの資料();
}
}
class 社員B implements なんかいい感じの仕事
{
public function なんかいい感じの資料を返す(): なんかいい感じの資料
{
// 評価をあげるための処理がごちゃごちゃごちゃごちゃ
return new なんかいい感じの資料('内容に関係ないけど見栄えのいいデザインー!');
}
}
$社員 = new 社員A();
$資料 = $社員->なんかいい感じの資料();
と、
$社員 = new 社員B();
$資料 = $社員->なんかいい感じの資料();
は、結局なんかいい感じの資料が返ってくるので、使用する側(会社側)からするとどちらを使用してもいい。
社員Aと社員Bは、なんかいい感じの仕事 interface のおかげで代替可能と言える。
依存関係の流れとして、実体の実装に依存関係が向くのではなく、interface に向いているこの作りは依存関係逆転の原則と呼ばれる。
interfaceでないとダメか?
継承ではダメか?という疑問が浮かびますが、継承で大丈夫かがしっかり設計出来ていれば、継承を使用するでもいいと思います。
継承は、以下の問題があります。
- 多重継承が出来ない
- 密結合になる
class, abstract が大きくなればなるほど無視出来なくなると思うので、使用する上では吟味が必要です。
(interface なら少しカジュアルに使えそうな温度感です。)
Discussion