んだ日記

ndaDayoの技術日記です

PHPで『リファクタリング―プログラムの体質改善テクニック』を実装してみる (6) メソッドの移動 

前回の記事 nda-desu.hatenablog.com

前回は、RentalクラスからMovieクラスにswitch文を移動させました。

<?php

namespace App;

use App\Rental;

class Movie
{
    
    ~省略~ 
    /**
     * ビデオのカテゴリとレンタル泊数に応じてポイントを返す
     *
     * @param $dayRented
     * @return float|int
     */
    public function getCharge($dayRented)
    {
        $result = 0;
        switch ($this->getPriceCode()) {
            case Movie::REGULAR:
                $result += 2;
                if ($dayRented > 2) {
                    $result += ($dayRented - 2) * 1.5;
                }
                break;
            case Movie::NEW_RELEASE:
                $result += $dayRented * 3;
                break;
            case Movie::CHILD:
                $result += 1.5;
                if ($dayRented > 3) {
                    $result += ($dayRented - 3) * 1.5;
                }
                break;
        }
        return $result;
    }
}

んが、、

まだ改善の余地はあるようです。

getChargeメソッドの責務を減らす

getChargeメソッドは、

・映画のカテゴリによって条件分岐させる

・映画のカテゴリとレンタル泊数によって返す値の計算まで行っています。

一つのメソッドにいろんなことをさせるのは、よくない。

メソッドの責務は必要最低限に。

金額の計算をPriceクラスに移動させる

ということで、計算の部分は他のクラスに移動させます。

Priceクラスというスーパークラスを作り、他のクラスはPriceクラスを継承する形で作っていきます。

こうすることで、

・映画のカテゴリが増えたときも ・金額の計算方法が変わったときも

修正がしやすくなる。

Priceクラス

<?php

namespace App\Price;

abstract class Price
{
    /**
     * @return mixed
     */
    abstract public function getPriceCode();

    /**
     * @param $dayRented
     * @return mixed
     */
    abstract public function getCharge($dayRented);

    /**
     * @param $dayRented
     * @return int
     */
    public function getFrequentRentalPoint($dayRented){
        return 1;
    }
}

RegularPriceクラス

<?php

namespace App\Price;

use App\Movie;

class RegularPrice extends Price
{
    /**
     * 一般向けの料金コードを返す
     *
     * @return int
     */
    public function getPriceCode()
    {
       return Movie::REGULAR;
    }

    /**
     * @param $dayRented
     * @return float|int|mixed
     */
    public function getCharge($dayRented)
    {
        $result = 2;

        if ($dayRented > 2) {
            $result += ($dayRented - 2) * 1.5;
        }

        return $result;
    }
}

NewReleasePriceクラス

<?php

namespace App\Price;

use App\Movie;

class NewReleasePrice extends Price
{
    /**
     * 新作の料金コードを返す
     *
     * @return int
     */
    public function getPriceCode()
    {
        return Movie::NEW_RELEASE;
    }

    /**
     * @param $dayRented
     * @return float|int
     */
    public function getCharge($dayRented)
    {
        return $dayRented * 3;
    }

    /**
     * @param $dayRented
     * @return int
     */
    public function getFrequentRentalPoint($dayRented)
    {
        return ($dayRented > 1) ? 2 : 1;
    }
}

ChildrenPriceクラス

<?php

namespace App\Price;

use App\Movie;

class ChildrenPrice extends Price
{
    /**
     * 子供向けビデオの料金コードを返す
     *
     * @return int
     */
    public function getPriceCode()
    {
        return Movie::CHILD;
    }

    public function getCharge($dayRented)
    {
        $result = 1.5;
        if ($dayRented > 3) {
            $result += ($dayRented - 3) * 1.5;
        }
        return $result;
    }
}

Movieクラス

あとは、Movieクラスも修正すればOK。

<?php

namespace App;

use App\Price\Price;
use App\Price\RegularPrice;
use App\Price\NewReleasePrice;
use App\Price\ChildrenPrice;


class Movie
{
    /** 一般向け
     *
     * @var int
     */
    const REGULAR = 0;

    /** 新着ビデオ
     *
     * @var int
     */
    const NEW_RELEASE = 1;

    /** 子供向け
     *
     * @var int
     */
    const CHILD = 2;

    /** @var string */
    private $title;

    /**
     * @var Price
     */
    private $price;

    public function __construct($title, $priceCode)
    {
        $this->title = $title;
        $this->setPriceCode($priceCode);
    }

    
 ~省略~
    /**
     * @return int
     */
    public function getPriceCode()
    {
        return $this->price->getPriceCode();
    }

    /**
     * 料金コードをセット
     *
     * @param $arg
     */
    public function setPriceCode($arg)
    {
        switch ($arg) {
            case Movie::REGULAR:
                $this->price = new RegularPrice();
                break;
            case Movie::NEW_RELEASE:
                $this->price = new NewReleasePrice();
                break;
            case Movie::CHILD:
                $this->price = new ChildrenPrice();
                break;
            default:
                throw new \InvalidArgumentException('不正な料金コード');
        }
    }

    /**
     * ビデオのカテゴリとレンタル泊数に応じてポイントを返す
     *
     * @param $dayRented
     * @return float|int
     */
    public function getCharge($dayRented)
    {
       return $this->price->getCharge($dayRented);
    }

    /**
     * 新作を2日以上借りた場合は、ボーナスポイント
     *
     * @param $dayRented
     * @return int
     */
    public function getFrequentRentalPoint($dayRented)
    {
        return $this->price->getFrequentRentalPoint($dayRented);
    }
}