ポリモーフィズム

ポリモーフィズムとは

オブジェクト指向のメリットとしてよく言われることの中にポリモーフィズムというのがあります。
日本語では多様性とか訳されます。

要は、異なる動作を同じ操作で実現することです。
家電でたとえてみましょう。
どのような家電も、動かすのにスイッチを押すという操作が共通します。しかし当然、スイッチを押す事による動作は家電ごとに違います。

ポリモーフィズムはこれをプログラミングに取り入れたようなものです。

本来ポリモーフィズムは型と深い関わりがありますが、
PHPにおいては型の存在意義が少し薄いので、Javaとかとは多少性格が異なります。
PHPの場合、一言で言えばクラスは異なっても同じ名前のメソッドでいろんな動きを実現させる事です。
それにはインターフェイスが絡んできます。

ポリモーフィズムの考え方

実は、インターフェイスの説明のところのサンプルコードがまさにポリモーフィズムです。カートと商品の例です。
この例では、カートのメソッドは、引数で受け取る商品クラスが、ある特定のメソッドを必ず持っている前提での処理になっており、それを保証するために商品クラスがインターフェイスを実装する、というものです。

インターフェイスや抽象クラスで、抽象メソッドとする理由はなんでしょう。

それは、必ず存在しなければならない手続きだけど、その中身は実装先のクラス内で好きなようにしていいよ、ってことです。異なる部分だけ実装を促すわけです。そうしておけば後の事は深く考えなくても良いってことです。

カートと商品の例だと、カートに商品を追加した瞬間に、商品のapplyPriceDownというメソッドが実行されますが、野菜の場合は常に3割引、肉の場合は特定の日のみ半額、冷凍食品に至っては何もしないというように、それぞれapplyPriceDownでやっている処理はバラバラです。

これがポリモーフィズムです。

タイプヒンティング

PHPでは型の曖昧さが一つの特徴です。
しかし、型を明確にしたい場合ってあります。

例えばインターフェイスのところのカートの例であれば、addProductメソッドは商品クラスのインスタンスを引数で受け取る事が前提になっています。しかし仮にこの引数になんか適当な、たとえば文字列とかを指定したらどうなるでしょう。当然おかしなことになります。

カートクラスにとっては、引数で受け取る変数は商品クラスインスタンスであることを保証したいところです。
それを実現するのが「タイプヒンティング」です。


  
    // 商品追加
    public function addProduct(Meet $product)
    {        
        $product->applyPriceDown();    
        $this->products[] = $product;
    }


カートのaddProductメソッドですが、引数の左にクラス名を記述します。
これで、引数$productはMeetクラスのインスタンス以外は受け付けなくなります。

この例の場合、Meetクラスはいいとしても、Vegetableクラスは受け付けてくれません。
しかし素晴らしいことに、このタイプヒンティングにはインターフェイスを指定する事が出来ます


    // 商品追加
    public function addProduct(IProduct $product)
    {        
        $product->applyPriceDown();    
        $this->products[] = $product;
    }

こうすると、引数にはIProductを実装するクラスのインスタンスならなんでも可能になります。つまりVegetableとMeetとFreezedFoodが可能になるのです。

ポリモーフィズムを意識してクラス設計する上では有効な仕組みです。

ちなみにこのタイプヒンティングにはクラスまたは配列のみ指定できます。普通のstringとかintとかいうような型は指定できません。

タイプヒンティングにより、以下のようなメリットがあるといえます。

  • クラス設計者はインターフェイスの仕様にもとづいてメソッドが作成できる
  • クラス使用者はメソッド実行の際の引数に何を渡せばよいかを調べる必要がない

つまり、クラス設計者の意図がクラス使用者に伝わりやすいという事です。

ポリモーフィズムのコーディング上の利点

実質的な利点な上のようなことですが、ポリモーフィズムをうまく利用するとコーディングが非常にシンプルに、綺麗になります。
本来ならifやswitch等で条件分岐を行い、長たらしくなりそうなロジックが、ポリモーフィズムを利用するとそのような条件分岐が排除され、シンプルになるわけです。

ポリモーフィズムを利用するパターンとしないパターンを比較してみましょう。

ポリモーフィズムなし
<?php

// 商品クラス
class Product
{
    const VEGETABLE = 'vegetable';
    const MEET = 'meet';
    const FREEZED = 'freezed';

    private $prices = array(
        self::VEGETABLE => 1000,
        self::MEET => 2000,
        self::FREEZED => 500,
    );

    private $productType;

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

    public function applyPriceDown()
    {
        switch ($this->productType) {
            case self::VEGETABLE:
                // 常に3割引
                $this->price = $this->price * 0.7;
                break;
            case self::MEET:
                // 29日なら半額
                if ('29' == date('d')) {
                    $this->price = $this->price / 2;
                }
                break;
        }
    }
}

// カートクラス
class Cart
{
    // 商品インスタンス保持用配列
    private $products = array();

    // 商品追加
    public function addProduct($product)
    {        
        $product->applyPriceDown();    
        $this->products[] = $product;
    }
}

$vegetable = new Product(Product::VEGETABLE);
$meet = new Product(Product::MEET);
$freezed = new Product(Product::FREEZED);

$cart = new Cart();
$cart->addProduct($vegetable);
$cart->addProduct($meet);
$cart->addProduct($freezed);

?>
ポリモーフィズムを利用
<?php

// 商品インターフェイス
interface IProduct
{
    public function applyPriceDown();
}

// 野菜クラス
class Begetable implements IProduct
{
    private $price = 1000;

    public function applyPriceDown()
    {
        // 常に3割引
        $this->price = $this->price * 0.7;
    }
}

// 肉クラス
class Meet implements IProduct
{
    private $price = 2000;

    public function applyPriceDown()
    {
        if ('29' == date('d')) {
            // 29日なら半額
            $this->price = $this->price / 2;
        }
    }
}

// 冷凍食品クラス
class FreezedFood implements IProduct
{
    private $price = 500;

    public function applyPriceDown()
    {
        // 値引きなし
    }
}

// カートクラス
class Cart
{
    // 商品インスタンス保持用配列
    private $products = array();

    // 商品追加
    public function addProduct($product)
    {        
        $product->applyPriceDown();    
        $this->products[] = $product;
    }
}

$vegetable = new Vegetable();
$meet = new Meet();
$freezed = new FreezedFood();

$cart = new Cart();
$cart->addProduct($vegetable);
$cart->addProduct($meet);
$cart->addProduct($freezed);

?>

ポリモーフィズムなしのパターンは少しapplyPriceDownが少しゴチャついた感じがしませんか?処理がごく簡単なものなので違いはまだ大した事ありませんが、大規模な処理になってくると違いが大きく出てきます。

対してポリモーフィズム利用ではクラスの数は増えますが、一つ一つがとても単純になっているのがわかると思います。
switchによる処理分岐がなくなったからです。

プログラミングって、条件分岐や制御構造が多いほど読みにくくなります。それを極力減らすという意味でもポリモーフィズムは有効です。