一. 接口隔離原則的定義
Clients should not be forced to depend upon interfaces that they don't use.
客戶(hù)端只依賴(lài)于它所需要的接口;它需要什么接口就提供什么接口,把不需要的接口剔除掉。
The dependency of one class to another one should depend on the smallest possible interface.
類(lèi)間的依賴(lài)關(guān)系應(yīng)建立在最小的接口上。
也就是說(shuō): 接口盡量細(xì)化,接口中的方法盡量少
二. 接口隔離原則和單一職責(zé)原則
從功能上來(lái)看,接口隔離原則和單一職責(zé)原則都是為了提高類(lèi)的內(nèi)聚, 降低類(lèi)之間的耦合, 體現(xiàn)了封裝的思想。但二者還是有區(qū)別的。
(1)從原則約束來(lái)看: 接口隔離原則更關(guān)注的是接口依賴(lài)程度的隔離;而單一職責(zé)原則更加注重的是接口職責(zé)的劃分。
(2)從接口的細(xì)化程度來(lái)看: 單一職責(zé)原則對(duì)接口的劃分更加精細(xì),而接口隔離原則注重的是相同功能的接口的隔離。接口隔離里面的最小接口有時(shí)可以是多個(gè)單一職責(zé)的公共接口。
(3)單一職責(zé)原則更加偏向?qū)I(yè)務(wù)的約束: 接口隔離原則更加偏向設(shè)計(jì)架構(gòu)的約束。這個(gè)應(yīng)該好理解,職責(zé)是根據(jù)業(yè)務(wù)功能來(lái)劃分的,所以單一原則更加偏向業(yè)務(wù);而接口隔離更多是為了“高內(nèi)聚”,偏向架構(gòu)的設(shè)計(jì)。
三. 接口隔離原則的優(yōu)點(diǎn)
接口隔離原則是為了約束接口、降低類(lèi)對(duì)接口的依賴(lài)性,遵循接口隔離原則有以下 5 個(gè)優(yōu)點(diǎn)。
- 將臃腫龐大的接口分解為多個(gè)粒度小的接口,可以預(yù)防外來(lái)變更的擴(kuò)散,提高系統(tǒng)的靈活性和可維護(hù)性。
- 接口隔離提高了系統(tǒng)的內(nèi)聚性,減少了對(duì)外交互,降低了系統(tǒng)的耦合性。
- 如果接口的粒度大小定義合理,能夠保證系統(tǒng)的穩(wěn)定性;然而,如果定義過(guò)小,則會(huì)造成接口數(shù)量過(guò)多,使設(shè)計(jì)復(fù)雜化;如果定義太大,靈活性降低,無(wú)法提供定制服務(wù),給整體項(xiàng)目帶來(lái)無(wú)法預(yù)料的風(fēng)險(xiǎn)。
- 使用多個(gè)專(zhuān)門(mén)的接口能夠體現(xiàn)對(duì)象的層次,因?yàn)榭梢酝ㄟ^(guò)接口的繼承,實(shí)現(xiàn)對(duì)總接口的定義。
- 能減少項(xiàng)目工程中的代碼冗余。過(guò)大的大接口里面通常放置許多不用的方法,當(dāng)實(shí)現(xiàn)這個(gè)接口的時(shí)候,被迫設(shè)計(jì)冗余的代碼。
四. 接口隔離原則的實(shí)現(xiàn)方法
在具體應(yīng)用接口隔離原則時(shí),應(yīng)該根據(jù)以下幾個(gè)規(guī)則來(lái)衡量。
1)接口要盡量小
不能出現(xiàn)Fat Interface;但是要有限度,首先不能違反單一職責(zé)原則(不能一個(gè)接口對(duì)應(yīng)半個(gè)職責(zé))。
2)接口要高內(nèi)聚
在接口中盡量少公布public方法。
接口是對(duì)外的承諾,承諾越少對(duì)系統(tǒng)的開(kāi)發(fā)越有利。
3)定制服務(wù)
只提供訪(fǎng)問(wèn)者需要的方法。例如,為管理員提供IComplexSearcher接口,為公網(wǎng)提供ISimpleSearcher接口。
4)接口的設(shè)計(jì)是有限度的
了解環(huán)境,拒絕盲從。每個(gè)項(xiàng)目或產(chǎn)品都有選定的環(huán)境因素,環(huán)境不同,接口拆分的標(biāo)準(zhǔn)就不同, 需要深入了解業(yè)務(wù)邏輯。
五. 接口隔離原則的建議
- 一個(gè)接口只服務(wù)于一個(gè)子模塊或業(yè)務(wù)邏輯;
- 通過(guò)業(yè)務(wù)邏輯壓縮接口中的public方法;
- 已被污染了的接口,盡量去修改;若變更的風(fēng)險(xiǎn)較大,則采用適配器模式轉(zhuǎn)化處理;
- 拒絕盲從
五. 案例分析
下面以學(xué)生成績(jī)管理為例來(lái)說(shuō)明接口隔離原則:
分析:學(xué)生成績(jī)管理程序一般包含查詢(xún)成績(jī)、新增成績(jī)、刪除成績(jī)、修改成績(jī)、計(jì)算總分、計(jì)算平均分、打印成績(jī)信息等功能,通常我們會(huì)怎么做呢?
一: 最初的設(shè)計(jì)
通常我們?cè)O(shè)計(jì)接口的方式如下:
public interface IStudentScore {
// 查詢(xún)成績(jī)
public void queryScore();
// 修改成績(jī)
public void updateScore();
// 添加成績(jī)
public void saveScore();
// 刪除成績(jī)
public void delete();
// 計(jì)算總分
public double sum();
// 計(jì)算平均分
public double avg();
// 打印成績(jī)單
public void printScore();
}
我們會(huì)吧所有的功能都放在一個(gè)接口里面. 這會(huì)產(chǎn)生什么樣的問(wèn)題呢?
首先, 接口的方法很多, 不利于擴(kuò)展. 比如: 學(xué)生只有查看成績(jī),打印成績(jī)單的權(quán)限, 沒(méi)有增刪改的權(quán)限; 老師擁有所有的權(quán)限.
查詢(xún)成績(jī)單:
package com.lxl.www.designPatterns.sixPrinciple.interfaceSegregationPrinciple.score;
public class QueryScore implements IStudentScore{
@Override
public void queryScore() {
// 查詢(xún)成績(jī)
}
@Override
public void updateScore() {
// 沒(méi)有權(quán)限
}
@Override
public void saveScore() {
// 沒(méi)有權(quán)限
}
@Override
public void delete() {
// 沒(méi)有權(quán)限
}
@Override
public double sum() {
// 沒(méi)有權(quán)限
return 0;
}
@Override
public double avg() {
// 沒(méi)有權(quán)限
return 0;
}
@Override
public void printScore() {
//打印成績(jī)單
}
}
操作成績(jī)單
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() {
}
}
可以看出問(wèn)題. 查詢(xún)成績(jī)單, 我們只會(huì)用到兩個(gè)方法, 可是因?yàn)閷?shí)現(xiàn)了接口, 不得不重寫(xiě)所有的方法.
如果這時(shí)候增加需求--發(fā)送給家長(zhǎng), 只有老師才有這個(gè)權(quán)限, 學(xué)生沒(méi)有這個(gè)權(quán)限. 可是, 在接口中增加一個(gè)抽象方法以后, 所有的實(shí)現(xiàn)類(lèi)都要重寫(xiě)這個(gè)方法. 這就違背了開(kāi)閉原則.
2. 使用接口隔離原則的設(shè)計(jì)
采用接口隔離原則設(shè)計(jì)的接口, UML圖如下:
public interface IQueryScore {
// 查詢(xún)成績(jī)
public void queryScore();
// 打印成績(jī)單
public void printScore();
}
public interface IOperateScore {
// 修改成績(jī)
public void updateScore();
// 添加成績(jī)
public void saveScore();
// 刪除成績(jī)
public void delete();
// 計(jì)算總分
public double sum();
// 計(jì)算平均分
public double avg();
}
public class StudentOperate implements IQueryScore{
@Override
public void queryScore() {
// 查詢(xún)成績(jī)
}
@Override
public void printScore() {
//打印成績(jī)單
}
}
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() {
}
}
我們將原來(lái)的一個(gè)接口進(jìn)行了接口拆分. 分為查詢(xún)接口和操作接口. 這樣學(xué)生端就不需要重寫(xiě)和他不相關(guān)的接口了.
如果將這些功能全部放到一個(gè)接口中顯然不太合理,正確的做法是將它們分別放在輸入模塊、統(tǒng)計(jì)模塊和打印模塊等 3 個(gè)模塊中,其類(lèi)圖如圖 1 所示