PHP の interface を使ったほうが良いかも知れないと思えるケースを考えてみた

どういったケースだと Interface を使ったタイプヒンティングが効果的になるのかを考えてみたメモ。

Sample4 で $spicyCurryUdon が Curry じゃないので、通らない。

Sample3 のように Interface で多重継承を行えば割りと素直にかけそう。

```sample.php
<?php

namespace Sample1
{
    interface HumanInterface
    {
        public function eat(FoodInterface $food);
    }

    class Human implements HumanInterface
    {
        public function eat(FoodInterface $food)
        {
            echo $food->name().' もぐもぐ'."\n";
        }
    }

    interface FoodInterface
    {
        public function name();
    }

    interface SobaInterface extends FoodInterface
    {

    }

    interface UdonInterface
    {
        // Udon は FoodInterface を extends してない
    }

    class Soba implements SobaInterface
    {
        private $name;

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

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

    class Udon implements UdonInterface
    {
        private $name;

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

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

    echo 'Sample1'."\n";
    echo 'Interface でタイプヒンティング'."\n";

    $human = new Human();

    $zaruSoba = new Soba('ざるそば');
    $moriSoba = new Soba('もりそば');

    $human->eat($zaruSoba);
    $human->eat($moriSoba);

    $zaruUdon = new Udon('ざるうどん');

    // $human->eat($zaruUdon); // Udon は最終的に FoodInterface を implements してないので、これはエラーとなる
    echo "\n";
}

namespace Sample2
{
    class Human
    {
        public function eat(Food $food)
        {
            echo $food->name().' もぐもぐ'."\n";
        }
    }

    abstract class Food
    {
        protected $name;

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

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

    class Soba extends Food
    {

    }

    class Udon
    {
        // Food を extends しない
        public function __construct($name)
        {
            $this->name = $name;
        }
    }

    echo 'Sample2'."\n";
    echo 'Class でタイプヒンティング'."\n";

    $human = new Human();

    $zaruSoba = new Soba('ざるそば');
    $moriSoba = new Soba('もりそば');

    $human->eat($zaruSoba);
    $human->eat($moriSoba);

    $zaruUdon = new Udon('ざるうどん');

    // $human->eat($zaruUdon); // Udon は最終的に Food を extends してないので、これはエラーとなる
    echo "\n";
}

namespace Sample3
{
    interface HumanInterface
    {
        public function eat(FoodInterface $food);
    }

    class Human implements HumanInterface
    {
        public function eat(FoodInterface $food)
        {
            echo $food->name().' もぐもぐ'."\n";
        }
    }

    interface FoodInterface
    {
        public function name();
    }

    interface NoodleInterface extends FoodInterface
    {

    }

    interface SobaInterface extends NoodleInterface
    {

    }

    interface UdonInterface extends NoodleInterface
    {

    }

    interface CurryInterface extends FoodInterface
    {

    }

    interface CurryUdonInterface extends CurryInterface, NoodleInterface
    {

    }

    abstract class Food implements FoodInterface
    {
        protected $name;

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

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

    class Soba extends Food implements SobaInterface
    {

    }

    class Udon extends Food implements UdonInterface
    {

    }

    class Curry extends Food implements CurryInterface
    {

    }

    class CurryUdon extends Food implements CurryUdonInterface
    {

    }

    echo 'Sample3'."\n";
    echo 'Interface の多重継承'."\n";

    $human = new Human();

    $zaruSoba = new Soba('ざるそば');
    $moriSoba = new Soba('もりそば');

    $zaruUdon = new Soba('ざるうどん');

    $butterChickenCurry = new Curry('バターチキンカレー');

    $spicyCurryUdon = new CurryUdon('ちょっと辛いカレーうどん');

    $human->eat($zaruSoba);
    $human->eat($moriSoba);
    $human->eat($zaruUdon);
    $human->eat($butterChickenCurry);
    $human->eat($spicyCurryUdon);

    class NoodleOnlyHuman
    {
        public function eat(NoodleInterface $noodle)
        {
            echo $noodle->name().' スルスル'."\n";
        }
    }

    $noodleHuman = new NoodleOnlyHuman();

    $noodleHuman->eat($zaruSoba);
    $noodleHuman->eat($moriSoba);
    $noodleHuman->eat($zaruUdon);
    // $noodleHuman->eat($butterChickenCurry); // $butterChickenCurry は Noodle じゃない
    $noodleHuman->eat($spicyCurryUdon);

    class CurryOnlyHuman
    {
        public function eat(CurryInterface $curry)
        {
            echo $curry->name().' ゴクゴク'."\n";
        }
    }

    $curryHuman = new CurryOnlyHuman();

    // $curryHuman->eat($zaruSoba); // $zaruSoba は Curry じゃない
    // $curryHuman->eat($moriSoba); // $moriSoba は Curry じゃない
    // $curryHuman->eat($zaruUdon); // $zaruUdon は Curry じゃない
    $curryHuman->eat($butterChickenCurry); // $butterChickenCurry は Noodle じゃない
    $curryHuman->eat($spicyCurryUdon);

    echo "\n";
}

namespace Sample4
{
    class Human
    {
        public function eat(Food $food)
        {
            echo $food->name().' もぐもぐ'."\n";
        }
    }

    abstract class Food
    {
        protected $name;

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

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

    abstract class Noodle extends Food
    {

    }

    class Soba extends Noodle
    {

    }

    class Udon extends Noodle
    {

    }

    class Curry extends Food
    {

    }

    class CurryUdon extends Noodle //, Curry の様に、複数の継承が出来ない
    {

    }

    echo 'Sample4'."\n";
    echo 'Class で Sample3 を再現しようと努力するけど、 $spicyCurryUdon が…'."\n";

    $human = new Human();

    $zaruSoba = new Soba('ざるそば');
    $moriSoba = new Soba('もりそば');

    $zaruUdon = new Soba('ざるうどん');

    $butterChickenCurry = new Curry('バターチキンカレー');

    $spicyCurryUdon = new CurryUdon('ちょっと辛いカレーうどん');

    $human->eat($zaruSoba);
    $human->eat($moriSoba);
    $human->eat($zaruUdon);
    $human->eat($butterChickenCurry);
    $human->eat($spicyCurryUdon);

    class NoodleOnlyHuman
    {
        public function eat(Noodle $noodle)
        {
            echo $noodle->name().' スルスル'."\n";
        }
    }

    $noodleHuman = new NoodleOnlyHuman();

    $noodleHuman->eat($zaruSoba);
    $noodleHuman->eat($moriSoba);
    $noodleHuman->eat($zaruUdon);
    // $noodleHuman->eat($butterChickenCurry); // $butterChickenCurry は Noodle じゃない
    $noodleHuman->eat($spicyCurryUdon);

    class CurryOnlyHuman
    {
        public function eat(Curry $curry)
        {
            echo $curry->name().' ゴクゴク'."\n";
        }
    }

    $curryHuman = new CurryOnlyHuman();

    // $curryHuman->eat($zaruSoba); // $zaruSoba は Curry じゃない
    // $curryHuman->eat($moriSoba); // $moriSoba は Curry じゃない
    // $curryHuman->eat($zaruUdon); // $zaruUdon は Curry じゃない
    $curryHuman->eat($butterChickenCurry);
    // $curryHuman->eat($spicyCurryUdon); // $spicyCurryUdon は Curry を継承できてない！

    echo "\n";
}
```