オーバーライド

オーバーライドとは

オーバーライド」とは、継承クラスにおいて、親クラスのメソッドを上書きする仕組みの事をいいます。親クラスのメソッドに追加機能を持たせたい場合、または親クラスのメソッドの機能を殺したい場合などに使用します。

オーバーライドの仕方

上書きと入っても、実はPHPにおいては何か特別な記述をするわけではありません。
親クラスに存在するメソッドと同じ名前のメソッドを作るだけです。

<?php

class Product
{
    protected $price;

    // 価格取得
    public function getPrice()
    {
        return $this->price;
    }

    // 価格設定
    public function setPrice($price)
    {
        $this->price = $price;
    }
}

class FoodProduct extends Product
{
    private $expire = 15;

    // 残りの賞味期限を取得
    public function getExpire()
    {
        return $this->expire;
    }

    // 残りの賞味期限を1減らす
    public function decrementExpire()
    {
        $this->expire--;
        return $this->getExpire();
    }

    // 価格取得のオーバーライド
    public function getPrice()
    {
        $price = $this->price;        
        if ($this->expire <= 10) {
            // 残り賞味期限が10日以下になったら半額
            $price = $price / 2;
        }
        return $price;
    }

}

$prd = new FoodProduct();
// 価格を100に設定
$prd->setPrice(100);

// 賞味期限残日数が0になるまで繰り返し
$expire = $prd->getExpire();
while ($expire > 0) {
    echo '賞味期限残:' . $expire . '日 価格:' . $prd->getPrice();
    $expire = $prd->decrementExpire();
}

?>
賞味期限残:14日 価格:100
賞味期限残:13日 価格:100
賞味期限残:12日 価格:100
賞味期限残:11日 価格:100
賞味期限残:10日 価格:50
賞味期限残:9日 価格:50
賞味期限残:8日 価格:50
賞味期限残:7日 価格:50
賞味期限残:6日 価格:50
賞味期限残:5日 価格:50
賞味期限残:4日 価格:50
賞味期限残:3日 価格:50
賞味期限残:2日 価格:50
賞味期限残:1日 価格:50
賞味期限残:0日 価格:50

ProductクラスにgetPriceメソッドを定義し、$priceフィールドの値を取得できるようにしています。

このProductクラスを継承してFoodProductクラスを定義していますが、Productクラスに既に存在するgetPriceメソッドをFoodProductクラスで再度定義しています。こうすることで親クラスのメソッドが上書きされます。

FoodProductクラスのgetPriceでは単純に$priceフィールドの値を返すだけではなく、$expireの値が10以下の場合は$priceの値の1/2の値を返します。この状態でFoodProductのインスタンスを生成し、$expireの値を1ずつ減らしていきつつ、都度価格を取得してみます。すると10以下になった時点で取得される価格が1/2になっているのが分かります。

つまりFoodProductでは、親クラスのgetPriceメソッドの内容を上書きし、FoodProductのgetPriceメソッドに記述された処理が実行されているのがわかります。

この状態は親クラスのメソッドの機能を完全に殺している事になります。

親クラスのメソッドを呼び出す

オーバーライドした場合、親クラスのメソッドは完全に消滅するのでしょうか。
実はそうではありません。継承クラスでオーバーライドすると、そちらが優先されるだけで、実は親クラスのメソッドはちゃんと存在しています。

どうやって呼び出すのか?
まずは下の例を見てください。

<?php

class Product
{
    protected $price;
    protected $discountRate = 0.03;

    // 価格取得
    public function getPrice()
    {       
        $price = $this->price - ($this->price * $this->discountRate);
       return $price;
    }

    // 価格設定
    public function setPrice($price)
    {
        $this->price = $price;
    }
}

?>

継承元となるProductクラスに少し変更を加えました。
まず、$discountRateとして、割引率のフィールドを追加しました。ここでは話を単純にするために0.03という固定値とします。
そしてgetPriceメソッドで、割引率分の割引を行った状態の価格を返すようにしました。

FoodProductですが、既にgetPriceをオーバーライドしているためにProductクラスのgetPriceメソッドの変更内容が反映されない事になります。
単純にFoodProductの方にも同じ処理を入れるという方法が思いつきますが、あまり好ましい事ではありません。これをやりだすと、親クラスの変更のたびに子クラスの方に同じ変更を加えなければならず、どちらか変更し忘れたらバグです。

できれば、親クラスの変更がそのまま子クラスにも反映されて欲しいところです。
そこで以下のようにします。

<?php

class FoodProduct extends Product
{
            ・
            ・
            ・

    // 価格取得のオーバーライド
    public function getPrice()
    {
        // 親クラスのgetPriceを呼び出し
        $price = parent::getPrice();
    
        if ($this->expire <= 10) {
            // 残り賞味期限が10日以下になったら半額
            $price = $price / 2;
        }
        return $price;
    }

            ・
            ・
            ・
}

?>

「parent::親クラスのメソッド名()」とすることで、親クラスのメソッドを呼び出すことが可能なのです。こうすれば、親クラスで何か変更があってもそのまま子クラスにも反映されることになります。

このように、親クラスの機能を「生かし」つつ、親クラスの機能に少し変更を加えるという場合は親クラスのメソッドを呼び出すという手法をとります。

もちろん、逆に親クラスの変更を反映したくない場合っていうのもあります。
そういう場合は最初の例のように、親クラスのメソッドをそのままコピーして、変更したい部分だけを書き換える、という感じになります。
これは親クラスの機能を「殺して」新たな機能を記述していることになります。

実質的な話、親クラスのメソッドは名前が同じなだけで別メソッドとも考えられます。そして名前が同じの場合は継承クラスのメソッドが優先して実行される、という感じです。