S.O.I.L.D 之單一職責

心智極客發表於2019-04-25

描述

單一職責 (Single Responsibility)

一個類應該僅有一個引起類變化的理由

反面示例

定義一個 SalesReporter 用於顯示某個時間段的銷售總額

<?php

namespace Acme\Reporting;

use Auth;
use DB;
use Exception;

class SalesReporter
{   
    /**
     * 獲取某個時間段的銷售總額
     * 
     * @param  $startDate
     * @param  $endDate  
     * 
     * @return string
     */
    public function between($startDate, $endDate) : string
    {
        if (! Auth::check()) {
            throw new Exception('Authentication required for reporting');
        }

        $sales = $this->queryDBForSalesBetween($startDate, $endDate);
        return $this->format($sales);
    }

    /**
     * 查詢銷售報表資料
     * 
     * @param  $startDate 
     * @param  $endDate  
     *  
     * @return float         
     */
    protected function queryDBForSalesBetween($startDate, $endDate) : float
    {
        return DB::table('sales')->whereBetween('created_at', [$startDate, $endDate])->sum('charge') / 100;
    }

    /**
     * 資料展示
     * 
     * @param  $sales
     * @return string     
     */
    protected function format($sales) : string
    {
        return "<h1>Sales: $sales</h1>";
    }
}

測試

$report = new Acme\Reporting\SalesReporter();
$report->between(
    now()->subDays(10), 
    now()
);

該例子明顯違反了單一職責:

  • 授權方式發生變化時,如 API 授權,需要改動該類
  • 當資料庫層發生變化時候,如使用 Redis 時,需要改動該類
  • 當展示方式發生變化時,需要改動該類

正面示例

對於上述的例子,應當作出如下的改動:

  • 不需要關心使用者授權,使用者授權與本類的職責無關
  • 資料層應當剝離出來
  • 展示層應當剝離出來
<?php

// 展示層介面
interface SalesOutputInterface {
    public function output();
}

// 展示層實現
class HtmlOutput implements SalesOutputInterface {
    public function output($sales)
    {
        echo "<h1>{$sales}</h1>";
    }
}

// 資料層
class SalesRepository {
    public function between()
    {
        return DB::table('sales')->whereBetween('create_at', [$startDate, $endDate])->sum('charge') / 100;
    }
}

// 職責類
class SalsReporter {

    public $repo;

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

    public function between($startDate, $endDate, SalesOutputInterface $formater)
    {
        $sales = $this->repo->between($startDate, $endDate);
        $formater->output($sales);
    }
}

測試

$report = new SalsReporter(new SalesRepository);
$report->between(
    now->subDays(10), 
    now(),
    new HtmlOutput
);

結合 Laravel 的依賴注入,可以進一步簡化

class SalsReporter {

    public $repo;

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

    public function between($startDate, $endDate, SalesOutputInterface $formater)
    {
        $sales = $this->repo->between($startDate, $endDate);
        $formater->output($sales);
    }
}

相關文章