设计模式实例讲解 - 单一职责

描述

单一职责 (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);
    }
}

来源:laracasts.com

本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!