使人瘋狂的 SOLID 原則: 介面隔離原則 (Interface Segregation Principle)
今天我們要說的是第四個原則:介面隔離原則 (ISP)。
如果還不知道什麼是介面的話,可以先看物件導向中的介面與抽象類別是什麼 ?
定義:No client should be forced to depend on methods it does not use.
白話翻譯:模組與模組之間的依賴,不應有用不到的功能可以被對方呼叫。
介面隔離原則其實並不困難。就如定義所說,因為模組之間的依賴不應有用不到的功能,所以我們可以透過介面來進行分割,把模組分得更合符本身的角色,也讓使用介面的角色只能分別接觸到應有的功能。
我先舉一個簡單的例子來說明何為違反 ISP:
class Car {
public function void openEngineMode() { /*...*/ }
public function void repairWheel() { /*...*/ } public function void startEngine() { /*...*/ }
public function void move() { /*...*/ }}class Driver {
Car myCar = new Car();
myCar.startEngine();
myCar.move();
myCar.openEngineMode(); // 為什麼我什麼都不會就可以開啟工程模式呢?
}class Mechanic {
Car clientCar = new Car();
clientCar.repairWheel();
clientCar.openEngineMode();
}
上例中,我們看到一個普通人就可以使用 Car 類別中的 openEngineMode() 功能,但這功能其實只是給工程師用的,普通人用很容易會把汽車用壞。這就是一個沒有把功能隔離好的例子。
Car 的部分功能給了不該用到這些功能的角色使用。
這時我們可以用 interface 來做優化,如下:
interface DailyUsage {
public function void startEngine();
public function void move();
}interface RepairUsage {
public function void openEngineMode();
public function void repairWheel();
}class Car implement DailyUsage, RepairUsage {
public function void openEngineMode() { /*...*/ }
public function void repairWheel() { /*...*/ } public function void startEngine() { /*...*/ }
public function void move() { /*...*/ }}class Driver {
DailyUsage myCar = new Car();
myCar.startEngine();
myCar.move();
}class Mechanic {
RepairUsage clientCar = new Car();
clientCar.repairWheel();
clientCar.openEngineMode();
}
透過 interface 來為我們的 Car 類別的不同功能分類,然後不同的角色再分別引用不同的功能。Mechanic 的程式將依賴於 RepairUsage 和 openEngineMode() ,但不再依賴於 Car。那 Mechanic 也不用關心 DailyUsage 和 Driver 的修改。
那架構層面來看呢?
好比當程式是依賴於 Framework 而 Framework 又賴依於 Database 時,當 Database 更換,如從 MySQL 換到 MongoDB,程式將會直接無法使用。
所以多使用介面來進行解藕、把實作隱藏起來、保持抽象,有助我們程式的彈性。