SOLID 原則 - Interface Segregation Principle (介面隔離原則)

1 minute read

Clients should not be forced to depend on methods that they do not use (A client should not be forced to implement an interface that it doesn’t use.) 客戶(類別)不應該實作沒用到的方法。

ISP的實作前是需要先釐清每個類別的需要功能。舉個例子來說,現在有個工人-雇主的關係。

我們先設定雇主需要的事情,最簡單的事需要管理工人,所以在manage方法內注入工人(worker),所以manage這個功能就會相依到worker這個類別會做的事情。

<?php

class Captain {
    public function manage(Worker $worker)
    {
        
    }
}

我們在來假設worker會作兩件事情,work及˙sleep,而我們在管理時需要讓工人時做這兩個方法。

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

目前到這個階段看來都算合理,如果接下來我們要雇用Android來工作呢?這時候我們可以先實作出一個介面出來,定義worker需要會哪些方法。

<?php
interface WorkerInterface {
    public function work()
    public function sleep()
}

接著我們實作人類工人HumanWorkerAndroid工人AndroidWroker

<?php
interface WorkerInterface {
    public function work();
    public function sleep();
}

class HumanWorker implements WorkerInterface{
    public function work()
    {
        
    }
    public function sleep()
    {
        
    }
}

class AndroidWroker implements WorkerInterface{
    public function work()
    {
        return 'Android work';
    }
    public function sleep()
    {
        return null;
    }
}
class Captain {
    public function manage(Worker $worker)
    {
        $worker->work();
        $worker->sleep();
    }
}

到這邊我們知道,AndroidWorker本身並不需要休息(機器人不需要休息),所以就違反了ISP,因為實作了沒有用到的方法(sleep),所以這個時候就可以把sleep這個方法抽離出來,另外做一個介面SleepableInterface,並只讓有休息功能的HumanWorker實做,AndroidWroker就只需要實作WorkableInterface

<?php
interface WorkableInterface {
    public function work();
}

interface SleepableInterface {
    public function sleep();
}

class HumanWorker implements WorkableInterface, SleepableInterface{
    public function work()
    {
        
    }
    public function sleep()
    {
        
    }
}

class AndroidWroker implements WorkableInterface{
    public function work()
    {
        return 'Android work';
    }
}

但是這樣到manage的功能就會報錯,因為AndroidWroker並沒有sleep的功能。而如果我們用判斷式處理,就會違反OCP原則,應該讓擴充容易,而不讓新增功能影響到原有的程式。

class Captain {
    public function manage(WorkableInterface $worker)
    {
        $worker->work();
        if($work istanceof AndroidWroker) return; // violate Open-Closed Principle
        $worker->sleep();
    }
}

所以這時候我們新增一個beManaged的介面,讓HumanWorker及AndroidWorker實作他們被管理時會用到的方法。

interface BeManageableInterface {
    public funtcion beManaged();
}

interface WorkableInterface {
    public function work();
}

interface SleepableInterface {
    public function sleep();
}

class HumanWorker implements WorkableInterface, SleepableInterface, BeManageableInterface{
    public function work()
    {
        
    }
    public function sleep()
    {
        
    }
    
    public function beManage()
    {
        $this->work();
        $this->sleep();
    }
}

class AndroidWroker implements WorkableInterface, BeManageableInterface{
    public function work()
    {
        return 'Android work';
    }
    
    public function beManage()
    {
        $this->work();
    }
}

最後,我們只要讓Manage這個方法注入BeManageableInterface介面,並使用$worker->beManage()方法就可以操作本來應該要有的功能。

class Captain {
    public function manage(BeManageableInterface $worker)
    {
        $worker->beManage();
    }
}

而之後若有其他的功能擴充,如其他的工人,所以就可以不用來修改manage的內容,反而需要修改被實作BeManageableInterface介面的物件,如此一來符合OCP也符合ISP

Categories:

Updated: