一. 接口隔離原則的定義
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 所示
