描述
單一職責 (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);
}
}