单一职责原则

简介

罗伯特·C·马丁在 21 世纪早期引入了名为「SOLID」的设计原则,指代了面向对象编程和面向对象设计的五个基本原则:

  • 单一职责原则(Single Responsibility Principle)
  • 开放封闭原则(Open Closed Principle)
  • 里氏替换原则(Liskov Substitution Principle)
  • 接口隔离原则(Interface Segregation Principle)
  • 依赖反转原则(Dependency Inversion Principle)

下面我们将深入探索以上每个设计原则,并且通过示例代码来阐述各个原则。我们将看到,每个原则之间都有联系。如果其中一个原则没有被遵循,那么其他大部分(可能不会是全部)的原则也会出问题。

实战

单一职责原则规定一个类有且仅有一个理由使其改变。换句话说,一个类的边界和职责应当是十分狭窄且集中的。我们之前就提到过,在类的职责问题上,无知是福。一个类应当做它该做的事,并且不应当被它的任何依赖的变化所影响。

考虑下面这个类:

class OrderProcessor 
{

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

    public function process(Order $order)
    {
        $recent = $this->getRecentOrderCount($order);

        if($recent > 0)
        {
            throw new Exception('Duplicate order likely.');
        }

        $this->biller->bill($order->account->id, $order->amount);

        DB::table('orders')->insert(array(
            'account'    =>    $order->account->id,
            'amount'    =>    $order->amount,
            'created_at'=>    Carbon::now()
        ));
    }

    protected function getRecentOrderCount(Order $order)
    {
        $timestamp = Carbon::now()->subMinutes(5);
        return DB::table('orders')->where('account', $order->account->id)
                                                ->where('created_at', '>=', $timestamps)
                                                ->count();
    }

}

上面这个类的职责是什么?很显然,顾名思义,它是用来处理订单的。但是,通过 getRecentOrderCount 方法,这个类又有了在数据库中审查某个帐号的订单历史以便判断是否有重复订单的职责。这个额外的验证职责意味着当我们的存储方式改变或者订单验证规则改变时,这个订单处理器也要跟着改变。

我们应该将这个验证职责提取出来放到其它类中,比如 OrderRepository 类:

class OrderRepository 
{

    public function getRecentOrderCount(Account $account)
    {
        $timestamp = Carbon::now()->subMinutes(5);
        return DB::table('orders')->where('account', $account->id)
                                                ->where('created_at', '>=', $timestamp)
                                                ->count();
    }

    public function logOrder(Order $order)
    {
        DB::table('orders')->insert(array(
            'account'    =>    $order->account->id,
            'amount'    =>    $order->amount,
            'created_at'=>    Carbon::now()
        ));
    }

}

然后,我们可以将这个仓库类注入到 OrderProcessor 里,以便分担后者对账户订单历史进行检查的责任:

class OrderProcessor {

    public function __construct(BillerInterface $biller, OrderRepository $orders)
    {
        $this->biller = $biller;
        $this->orders = $orders;
    }

    public function process(Order $order)
    {
        $recent = $this->orders->getRecentOrderCount($order->account);

        if($recent > 0)
        {
            throw new Exception('Duplicate order likely.');
        }

        $this->biller->bill($order->account->id, $order->amount);

        $this->orders->logOrder($order);
    }
}

现在,我们已经提取出了收集订单数据的职责,当获取和记录订单的方法改变时,就不再需要修改 OrderProcessor 这个类了。我们的类的职责变得更加专注和集中,同时也让代码变得更简洁、更优雅、更容易维护。

需要注意的是,单一职责原则的核心并不仅仅是让代码变短,而是要写出职责更加明确、方法更加内聚的类,所以要确保类里面所有的方法都隶属于该类的职责之内。在构建一个小巧、清晰且职责明确的类库以后,我们的代码会更加解耦,更容易测试,并且更易于修改。

上一篇: 框架扩展篇

下一篇: 开放封闭原则