一. 介面隔離原則的定義
Clients should not be forced to depend upon interfaces that they don't use.
客戶端只依賴於它所需要的介面;它需要什麼介面就提供什麼介面,把不需要的介面剔除掉。
The dependency of one class to another one should depend on the smallest possible interface.
類間的依賴關係應建立在最小的介面上。
也就是說: 介面儘量細化,介面中的方法儘量少
二. 介面隔離原則和單一職責原則
從功能上來看,介面隔離原則和單一職責原則都是為了提高類的內聚, 降低類之間的耦合, 體現了封裝的思想。但二者還是有區別的。
(1)從原則約束來看: 介面隔離原則更關注的是介面依賴程度的隔離;而單一職責原則更加註重的是介面職責的劃分。
(2)從介面的細化程度來看: 單一職責原則對介面的劃分更加精細,而介面隔離原則注重的是相同功能的介面的隔離。介面隔離裡面的最小介面有時可以是多個單一職責的公共介面。
(3)單一職責原則更加偏向對業務的約束: 介面隔離原則更加偏向設計架構的約束。這個應該好理解,職責是根據業務功能來劃分的,所以單一原則更加偏向業務;而介面隔離更多是為了“高內聚”,偏向架構的設計。
三. 介面隔離原則的優點
介面隔離原則是為了約束介面、降低類對介面的依賴性,遵循介面隔離原則有以下 5 個優點。
- 將臃腫龐大的介面分解為多個粒度小的介面,可以預防外來變更的擴散,提高系統的靈活性和可維護性。
- 介面隔離提高了系統的內聚性,減少了對外互動,降低了系統的耦合性。
- 如果介面的粒度大小定義合理,能夠保證系統的穩定性;然而,如果定義過小,則會造成介面數量過多,使設計複雜化;如果定義太大,靈活性降低,無法提供定製服務,給整體專案帶來無法預料的風險。
- 使用多個專門的介面能夠體現物件的層次,因為可以通過介面的繼承,實現對總介面的定義。
- 能減少專案工程中的程式碼冗餘。過大的大介面裡面通常放置許多不用的方法,當實現這個介面的時候,被迫設計冗餘的程式碼。
四. 介面隔離原則的實現方法
在具體應用介面隔離原則時,應該根據以下幾個規則來衡量。
1)介面要儘量小
不能出現Fat Interface;但是要有限度,首先不能違反單一職責原則(不能一個介面對應半個職責)。
2)介面要高內聚
在介面中儘量少公佈public方法。
介面是對外的承諾,承諾越少對系統的開發越有利。
3)定製服務
只提供訪問者需要的方法。例如,為管理員提供IComplexSearcher介面,為公網提供ISimpleSearcher介面。
4)介面的設計是有限度的
瞭解環境,拒絕盲從。每個專案或產品都有選定的環境因素,環境不同,介面拆分的標準就不同, 需要深入瞭解業務邏輯。
五. 介面隔離原則的建議
- 一個介面只服務於一個子模組或業務邏輯;
- 通過業務邏輯壓縮介面中的public方法;
- 已被汙染了的介面,儘量去修改;若變更的風險較大,則採用介面卡模式轉化處理;
- 拒絕盲從
五. 案例分析
下面以學生成績管理為例來說明介面隔離原則:
分析:學生成績管理程式一般包含查詢成績、新增成績、刪除成績、修改成績、計算總分、計算平均分、列印成績資訊等功能,通常我們會怎麼做呢?
一: 最初的設計
通常我們設計介面的方式如下:
public interface IStudentScore {
// 查詢成績
public void queryScore();
// 修改成績
public void updateScore();
// 新增成績
public void saveScore();
// 刪除成績
public void delete();
// 計算總分
public double sum();
// 計算平均分
public double avg();
// 列印成績單
public void printScore();
}
我們會吧所有的功能都放在一個介面裡面. 這會產生什麼樣的問題呢?
首先, 介面的方法很多, 不利於擴充套件. 比如: 學生只有檢視成績,列印成績單的許可權, 沒有增刪改的許可權; 老師擁有所有的許可權.
查詢成績單:
package com.lxl.www.designPatterns.sixPrinciple.interfaceSegregationPrinciple.score;
public class QueryScore implements IStudentScore{
@Override
public void queryScore() {
// 查詢成績
}
@Override
public void updateScore() {
// 沒有許可權
}
@Override
public void saveScore() {
// 沒有許可權
}
@Override
public void delete() {
// 沒有許可權
}
@Override
public double sum() {
// 沒有許可權
return 0;
}
@Override
public double avg() {
// 沒有許可權
return 0;
}
@Override
public void printScore() {
//列印成績單
}
}
操作成績單
package com.lxl.www.designPatterns.sixPrinciple.interfaceSegregationPrinciple.score;
public class Operate implements IStudentScore{
@Override
public void queryScore() {
}
@Override
public void updateScore() {
}
@Override
public void saveScore() {
}
@Override
public void delete() {
}
@Override
public double sum() {
return 0;
}
@Override
public double avg() {
return 0;
}
@Override
public void printScore() {
}
}
可以看出問題. 查詢成績單, 我們只會用到兩個方法, 可是因為實現了介面, 不得不重寫所有的方法.
如果這時候增加需求--傳送給家長, 只有老師才有這個許可權, 學生沒有這個許可權. 可是, 在介面中增加一個抽象方法以後, 所有的實現類都要重寫這個方法. 這就違背了開閉原則.
2. 使用介面隔離原則的設計
採用介面隔離原則設計的介面, UML圖如下:
public interface IQueryScore {
// 查詢成績
public void queryScore();
// 列印成績單
public void printScore();
}
public interface IOperateScore {
// 修改成績
public void updateScore();
// 新增成績
public void saveScore();
// 刪除成績
public void delete();
// 計算總分
public double sum();
// 計算平均分
public double avg();
}
public class StudentOperate implements IQueryScore{
@Override
public void queryScore() {
// 查詢成績
}
@Override
public void printScore() {
//列印成績單
}
}
public class TeacherOperate implements IQueryScore, IOperateScore{
@Override
public void queryScore() {
}
@Override
public void updateScore() {
}
@Override
public void saveScore() {
}
@Override
public void delete() {
}
@Override
public double sum() {
return 0;
}
@Override
public double avg() {
return 0;
}
@Override
public void printScore() {
}
}
我們將原來的一個介面進行了介面拆分. 分為查詢介面和操作介面. 這樣學生端就不需要重寫和他不相關的介面了.
如果將這些功能全部放到一個介面中顯然不太合理,正確的做法是將它們分別放在輸入模組、統計模組和列印模組等 3 個模組中,其類圖如圖 1 所示