重構 第一個示例

周小帥發表於2019-09-06

重構之前的程式碼

<?php
/**
 * Created by PhpStorm.
 * User: Monkey
 * Date: 2019-08-27
 * Time: 22:57
 */

$playsStr = '{
    "hamlet": {"name": "Hamlet", "type": "tragedy"},
    "as-like": {"name": "As You Like It", "type": "comedy"},
    "othello": {"name": "Othello", "type": "tragedy"}
}';

$invoicesStr =
    '[
        {
            "customer": "BigCo",
            "performances": [
                {
                    "playID": "hamlet",
                    "audience": 55
                },
                {
                    "playID": "as-like",
                    "audience": 35
                },
                {
                    "playID": "othello",
                    "audience": 40
                }
            ]
        }
    ]';

$plays = json_decode($playsStr, true);

$invoices = json_decode($invoicesStr, true);

/** 
 * @param $invoices
 * @param $plays
 * @return string
 */
function statement($invoices, $plays)
{
    $totalAmount   = 0;
    $volumeCredits = 0;
    $result = "Statement for {$invoices[0]['customer']} " . PHP_EOL;

    $perf = $invoices[0]['performances'];

    foreach ($perf as $key => $value) {

        $thisAmount = 0;
        switch ($plays[$value['playID']]['type']) {
            case 'tragedy' :
                $thisAmount = 40000;

                if ($value['audience'] > 30) {
                    $thisAmount += 1000 * ($value['audience'] - 30);
                }
                break;

            case 'comedy' :
                $thisAmount = 30000;
                if ($value['audience'] > 20) {
                    $thisAmount += 10000 + 500 * ($value['audience'] - 20);
                }
                $thisAmount += 300 * $value['audience'];
                break;

            default:

                echo("unknown type: {$plays[$value['playID']]['type']}");
        }
        // add volume credits
        $volumeCredits += max($value['audience'] - 30, 0);
        // add extra credit for every ten comedy attendees”

        if ("comedy" === $plays[$value['playID']]['type']) {
            $volumeCredits += floor($value['audience'] / 5);
        }

        // print line for this order
        $result .= " {" . $plays[$value['playID']]['name'] . "}:\$" . number_format($thisAmount / 100) . " ({$value['audience']} seats)" . PHP_EOL;

        $totalAmount += $thisAmount;
    }
    $result .= "Amount owed is \$" . number_format($totalAmount / 100) . PHP_EOL;
    $result .= "You earned {$volumeCredits} credits" . PHP_EOL;

    return $result;
}

echo statement($invoices, $plays);
重構後
<?php

class Show
{
    /**
     * @var
     */
    public $plays;
    /**
     * @var
     */
    public $invoices;

    /**
     * Show constructor.
     * @param $invoices
     * @param $plays
     */
    public function __construct($invoices, $plays)
    {
        $this->invoices = $invoices;
        $this->plays = $plays;
    }

    /**
     * @return string
     */
    public function statement()
    {
        $result = "Statement for {$this->invoices[0]['customer']} " . "<br />";
        $perf = $this->invoices[0]['performances'];

        foreach ($perf as $key => $value) {
            $result .=  $this->playFor($value)['name'] . " :\$" . $this->usd($this->amountFor($value)) . " { ({$value['audience']} seats)  } ," . "<br />";
        }

        $result .= "Amount owed is \$" . $this->usd($this->totalAmount()) . "<br />";
        $result .= "You earned {$this->totalVolumeCredits()} credits" . "<br />";

        return $result;
    }

    /**
     * @return float|int|mixed
     */
    public function totalVolumeCredits()
    {
        $result = 0;
        foreach ($this->invoices[0]['performances'] as $item => $val) {
            $result += $this->volumeCreditsFor($val);
        }

        return $result;
    }

    /**
     * @param $number
     * @return string
     */
    public function usd($number)
    {
        return number_format($number / 100);
    }

    /**
     * @param $value
     * @return float|int|mixed
     */
    public function volumeCreditsFor($value)
    {
        $result = 0;

        $result += max($value['audience'] - 30, 0);

        if ("comedy" === $this->playFor($value)['type']) {
            $result += floor($value['audience'] / 5);
        }

        return $result;
    }

    /**
     * @param $value
     * @return float|int
     */
    public function amountFor($value)
    {
        $result = 0;
        switch ($this->playFor($value)['type']) {
            case 'tragedy' :
                $result = 40000;
                if ($value['audience'] > 30) {
                    $result += 1000 * ($value['audience'] - 30);
                }
                break;
            case 'comedy' :
                $result = 30000;
                if ($value['audience'] > 20) {
                    $result += 10000 + 500 * ($value['audience'] - 20);
                }
                $result += 300 * $value['audience'];
                break;
            default:
                echo("unknown type: {$this->playFor($value)['type']}");
        }

        return $result;
    }

    /**
     * @param $value
     * @return mixed
     */
    public function playFor($value)
    {
        return $this->plays[$value['playID']];
    }

    /**
     * @return float|int
     */
    private function totalAmount()
    {
        $result   = 0;
        foreach ($this->invoices[0]['performances'] as $key => $value) {
            $result += $this->amountFor($value);
        }

        return $result;
    }

}

$playsStr = '{
    "hamlet": {"name": "Hamlet", "type": "tragedy"},
    "as-like": {"name": "As You Like It", "type": "comedy"},
    "othello": {"name": "Othello", "type": "tragedy"}
}';

$invoicesStr =
    '[
        {
            "customer": "BigCo",
            "performances": [
                {
                    "playID": "hamlet",
                    "audience": 55
                },
                {
                    "playID": "as-like",
                    "audience": 35
                },
                {
                    "playID": "othello",
                    "audience": 40
                }
            ]
        }
    ]';

$plays = json_decode($playsStr, true);

$invoices = json_decode($invoicesStr, true);

$show = new Show($invoices, $plays);

echo $show->statement();
增加一個新需求:將演出資訊列印到 html 中
<?php
/**
 * Created by PhpStorm.
 * User: Monkey
 * Date: 2019-08-28
 * Time: 20:47
 */

class Show
{
    /**
     * @var
     */
    public $plays;
    /**
     * @var
     */
    public $invoices;

    /**
     * Show constructor.
     * @param $invoices
     * @param $plays
     */
    public function __construct($invoices, $plays)
    {
        $this->invoices = $invoices;
        $this->plays = $plays;
    }

    /**
     *  準備兩個業務需要的資料;
     *
     *  這樣不同業務只需要關係業務本身,不用關注資料問題;
     *
     * @return array
     */
    public function createStatementData()
    {
        $result = [];
        $result['customer'] = $this->invoices[0]['customer'];
        $result['performances'] = $this->getPerformances($this->invoices[0]['performances']);
        $result['totalAmount'] = $this->totalAmount($result);
        $result['totalVolumeCredits'] = $this->totalVolumeCredits($result);

        return $result;
    }

    /**
     * 準備 performances 資料
     *
     * @param $performanceses
     * @return array
     */
    public function getPerformances($performanceses)
    {
        return array_map(function ($result) {
            $result['play'] = $this->playFor($result);
            $result['amount'] = $this->amountFor($result);
            $result['volumeCredits'] = $this->volumeCreditsFor($result);

            return $result;
        }, $performanceses);
    }

    /**
     * 輸出宣告
     *
     * @return string
     */
    public function statement()
    {
        return $this->renderPlainText($this->createStatementData());
    }

    /**
     * 列印 html 宣告
     * 
     * @return string
     */
    public function renderStatement()
    {
        return $this->renderHtml($this->createStatementData());
    }

    /**
     *  列印
     *
     * @param $data
     * @return string
     */
    public function renderHtml($data)
    {
        $result = "<h1>Statement for \${" . $data['customer'] . "}</h1>\n";
        $result .= "<table>\n";
        $result .= "<tr><th>play</th><th>seats</th><th>cost</th></tr>";

        foreach ($data['performances'] as $value) {
                $result .= " <tr><td>\${" . $value['play']['name'] . "}</td><td>\${" . $value['audience'] . "}</td>";
                $result .= "<td>\${" . $this->usd($this->amountFor($value)) . "}</td></tr>\n";
        }

        $result .= "</table>\n";
        $result .= "<p>Amount owed is <em>\${" . $this->usd($data['totalAmount']) . "}</em></p>\n";
        $result .= "<p>You earned <em>\${" . $data['totalVolumeCredits'] . "}</em> credits</p>\n";

        return $result;

    }

    /**
     *  輸出
     * 
     * @param $statementData
     * @return string
     */
    public function renderPlainText($statementData)
    {
        $result = "Statement for {$this->invoices[0]['customer']} <br />" ;
        foreach ($statementData['performances'] as $key => $value) {
            $result .=  $value['play']['name'] . " :\$" . $this->usd($this->amountFor($value)) . " { ({$value['audience']} seats)  } ," . "<br />";
        }

        $result .= "Amount owed is \$" . $this->usd($this->totalAmount($statementData)) . "<br />";
        $result .= "You earned {$this->totalVolumeCredits($statementData)} credits" . "<br />";

        return $result;
    }

    /**
     *  總積分
     * 
     * @param $statementData
     * @return float|int|mixed
     */
    public function totalVolumeCredits($statementData)
    {
        $result = 0;
        foreach ($statementData['performances'] as $item => $val) {
            $result += $this->volumeCreditsFor($val);
        }

        return $result;
    }

    /**
     *  格式化金額
     *
     * @param $number
     * @return string
     */
    public function usd($number)
    {
        return number_format($number / 100);
    }

    /**
     *  積分
     *
     * @param $value
     * @return float|int|mixed
     */
    public function volumeCreditsFor($value)
    {
        $result = 0;

        $result += max($value['audience'] - 30, 0);

        if ("comedy" === $this->playFor($value)['type']) {
            $result += floor($value['audience'] / 5);
        }

        return $result;
    }

    /**
     *  金額
     * 
     * @param $value
     * @return float|int
     */
    public function amountFor($value)
    {
        $result = 0;
        switch ($this->playFor($value)['type']) {
            case 'tragedy' :
                $result = 40000;
                if ($value['audience'] > 30) {
                    $result += 1000 * ($value['audience'] - 30);
                }
                break;
            case 'comedy' :
                $result = 30000;
                if ($value['audience'] > 20) {
                    $result += 10000 + 500 * ($value['audience'] - 20);
                }
                $result += 300 * $value['audience'];
                break;
            default:
                echo("unknown type: {$this->playFor($value)['type']}");
        }

        return $result;
    }

    /**
     *  放映
     * 
     * @param $value
     * @return mixed
     */
    public function playFor($value)
    {
        return $this->plays[$value['playID']];
    }

    /**
     *  總金額
     * 
     * @param $statementData
     * @return float|int
     */
    private function totalAmount($statementData)
    {
        $result   = 0;
        foreach ($statementData['performances'] as $key => $value) {
            $result += $this->amountFor($value);
        }

        return $result;
    }

}

$playsStr = '{
    "hamlet": {"name": "Hamlet", "type": "tragedy"},
    "as-like": {"name": "As You Like It", "type": "comedy"},
    "othello": {"name": "Othello", "type": "tragedy"}
}';

$invoicesStr =
    '[
        {
            "customer": "BigCo",
            "performances": [
                {
                    "playID": "hamlet",
                    "audience": 55
                },
                {
                    "playID": "as-like",
                    "audience": 35
                },
                {
                    "playID": "othello",
                    "audience": 40
                }
            ]
        }
    ]';

$plays = json_decode($playsStr, true);

$invoices = json_decode($invoicesStr, true);

$show = new Show($invoices, $plays);

echo $show->statement();

echo $show->renderStatement();

使用方法:
使用拆分迴圈:分離出累加過程

使用移動語句:將累加的宣告與累加過程集中在一起

使用提煉函式:提煉出計算總數的函式

使用內聯變數:完全移除中間的變數

重構注意事項:
重構需要小步進行,每次小步進行後進行測試,知道測試透過後再進行下一小步重構;

本作品採用《CC 協議》,轉載必須註明作者和本文連結
周小帥

相關文章