設計原則 - 介面隔離原則 Interface Segregation Principle

想像一下,你的程式裡有一個大型介面,每次新增功能都得修改這個介面,這樣不僅讓開發變得複雜,也讓維護變得困難。這就是為什麼我們需要「介面隔離原則 (Interface Segregation Principle, ISP)」。這個原則告訴我們,與其讓程式依賴一個龐大且複雜的介面,不如讓它們只依賴於它們真正需要的介面,這樣可以讓程式碼更加靈活和容易維護。

什麼是介面隔離原則?

介面隔離原則的核心概念是:不要強迫一個類別實作它不需要的介面方法。這意味著我們應該將大的介面拆分為更小、更具針對性的介面,讓類別只依賴它們所需要的功能。

想像一下,如果你是一名廚師,而你的工作清單裡除了煮飯,還包含了洗衣、修車等不相關的工作,你肯定會覺得困擾。對於軟體設計來說,大型介面就像這樣的工作清單,讓不必要的依賴進入系統,增加了複雜性。

介面隔離原則的例子

讓我們用一個具體例子來看,什麼樣的設計會違反介面隔離原則。

錯誤的設計,違反介面隔離原則

假設我們有一個 Worker 介面,裡面定義了各種工作相關的功能,包括寫程式、做簡報、煮飯等:

1
2
3
4
5
6
class IWorker {
public:
virtual void code() = 0;
virtual void cook() = 0;
virtual void present() = 0;
};

現在,如果有一個 Programmer 類別需要實作這個介面,它只需要寫程式,但仍然得實作 cookpresent 方法,這些它根本不會用到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Programmer : public IWorker {
public:
void code() override {
// 寫程式的邏輯
}

void cook() override {
// 不需要,但必須實作
}

void present() override {
// 不需要,但必須實作
}
};

這種設計顯然是違反了 ISP 的,因為 Programmer 類別被強迫實作了它根本不需要的功能。這不僅讓程式碼冗長,還會帶來未來維護的麻煩。

正確的設計,遵守介面隔離原則

正確的設計應該將這些功能分離為多個小介面,讓類別只實作它所需要的功能。例如,我們可以這樣做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class IProgrammer {
public:
virtual void code() = 0;
};

class ICook {
public:
virtual void cook() = 0;
};

class IPresenter {
public:
virtual void present() = 0;
};

現在,Programmer 類別只需要實作 IProgrammer 介面,完全不需要理會與它無關的 cookpresent 方法:

1
2
3
4
5
6
class Programmer : public IProgrammer {
public:
void code() override {
// 寫程式的邏輯
}
};

這樣的設計遵守了介面隔離原則,讓類別只專注於它該做的事情,減少了不必要的依賴和負擔。

為什麼這麼做?

遵守 ISP 有很多好處。首先它讓程式碼更加模組化和易於理解。當介面變小、變專注時,開發者可以更容易地了解每個介面的職責,這減少了混亂。

其次,這樣的設計讓我們更容易進行修改和擴展。假如未來需要新增功能,只需修改特定的小介面,而不會影響其他不相關的部分。這讓系統更加穩定,減少了修改時引入錯誤的風險。

再者,遵守 ISP 讓我們可以避免不必要的依賴。當類別依賴於一個龐大的介面時,即便只使用其中一部分功能,它仍然會受到整個介面的影響。這種依賴會讓程式變得臃腫且難以維護。

然而,過度拆分介面也可能帶來額外的複雜性。因此,我們在實踐 ISP 時,應該保持平衡,確保每個介面都有明確的職責,而不過度細化。

實際應用

在大型專案中,遵守介面隔離原則是保持程式碼品質和穩定性的關鍵。例如,在開發圖形使用者介面 (GUI) 時,按鈕、下拉選單等 UI 元件可能會有不同的互動方式。將這些行為拆分成小介面,能夠讓每個元件專注於它自己的功能,而不會被不必要的行為所束縛。

另一個應用場景是在大型企業軟體開發中,當需要同時面對不同的使用者需求時,我們可以透過小介面來應對不同的需求變化,確保每個功能都能獨立變動,保持靈活性。