S.O.I.L.D 之介面隔離

心智極客發表於2019-05-30

說明

介面隔離原則的通俗描述如下

客戶端不應當被迫實現它不需要用到的介面

不好的示例

工人包括工作和睡覺兩種行為

class Worker {
    public function work()
    {

    }
    public function sleep()
    {

    }
}

機長則負責管理生產,需要管理生產要素

class Captain {
    public function manage(Worker $worker)
    {
        $worker->work();
        $worker->sleep();
    }
}

但是生產要素不僅僅包括工人,機器也算。通常,我們會約定好「生產要素」

interface WorkerInterface {
    public function work();
    public function sleep();
}

再分別定義人和機器

class HumanWorker implements WorkerInterface {
    public function work()
    {
        return 'human working'
    }
    public function sleep()
    {
        return 'human sleeping';
    }
}

class AndroidWorker implements WorkerInterface {
    public function work()
    {
        return 'Android working';
    }
    public function sleep()
    {
        return null;
    }
}

現在,問題來了。機器沒有 sleep 這個行為,卻被迫去實現該介面。這明顯違反了介面隔離原則。

改進 1

首先,我們想到以將「工作」和「睡覺」兩個行為分別隔離出來

工作介面

interface WorkableInterface
{
    public function work();
}

睡覺介面

interface SleepableInterface
{
    public function sleep();
}

工人和機器根據自身情況去實現這些介面

class HumanWorker implements WorkableInterface, SleepableInterface
{
    public function work()
    {
        return 'human working.';
    }

    public function sleep()
    {
        return 'human sleeping';
    }
}

class AndroidWorker implements WorkableInterface
{
    public function work()
    {
        return 'android working.';
    }

    public function beManaged()
    {
        $this->work();
    }
}

機長

class Captain
{
    public function manage($worker)
    {
        if($worker instanceof WorkableInterface){
            $worker->work();
        } else if($worker instanceof SleepableInterface){
            $worker->sleep();
        }
    }
}

雖然我們分離了工作和睡覺兩個行為,避免了違反介面隔離原則。但是該例子仍然有問題,我們可以看出, Caption 類對 manage 是開放的,一旦新增新的生產要素,就必須去修改該程式碼,很明顯,違背了開放封閉原則。

最終改進

Captain 的變化的行為為 manage,將其分離出來

interface WorkableInterface
{
    public function work();
}

interface ManageableInterface
{
    public function beManaged();
}

對應的生產要素分別實現相應的介面

class HumanWorker implements WorkableInterface, SleepableInterface, ManageableInterface
{
    public function work()
    {
        return 'human working.';
    }
    public function sleep()
    {
        return 'human sleeping';
    }
    public function beManaged()
    {
        $this->work();
        $this->sleep();
    }
}

class AndroidWorker implements WorkableInterface, ManageableInterface
{
    public function work()
    {
        return 'android working.';
    }
    public function beManaged()
    {
        $this->work();
    }
}

保持 Captain 類的封閉性

class Captain
{
    public function manage(ManageableInterface $worker)
    {
        $worker->beManaged();
    }
}

相關文章