設計原則之介面分離原則(ISP)
簡介
單一職責原則傾向於設計視角,介面分離原則傾向於實現視角,二者看起來非常相似,但是在某些方面還是有所區別的。
定義
介面分離原則(Interface Segregation Principle):使用方不應該依賴於它不使用的方法(no client should be forced to depend on methods it does not use.)。
ISP 原則是用來處理胖介面或胖基類的,介面或類中包含了茫茫多的方法就稱為胖介面或胖基類(簡稱小胖吧)。使用方在使用小胖的時候,會發現自己沒有必要實現某個方法,但由於在小胖中存在,自己不得不實現一個,要麼是空方法,要麼丟擲異常以表明自己不支援。
這時候就需要 ISP 原則出場了,它指導你將介面劃分成更小的粒度,使用方只需要實現自己需要的介面即可,而不用繼承小胖導致不得不實現小胖交代下來的任務。
實踐
在設計原則只里氏替換原則中,我們舉的例子就違反了介面分離原則,這裡再舉一個例子說明這個原則。
需求要求我們做一個二手書設計,要求我們記錄書的基本資訊,收購的基本資訊,以及二手書的鑑定資訊等資訊。
設計的介面如下:
public interface Book { public String isbn(); public String author(); public Date publishDate(); public PublisherInfo publisher(); public PurchaseInfo purchaseInfo(); public IdentificationInfo identificationInfo(); }
介面工作良好,很好的支援了網站的執行。但由於業務的變化,網站現在不僅僅要賣二手書了,還要賣新書。這時只是缺少了收購資訊和鑑定資訊,但是新書本質上還是書,因此我們直接實現了 Book
介面來賣新書。
public abstract class NewBook implements Book {
public PurchaseInfo purchaseInfo() {
return null;
}
public IdentificationInfo identificationInfo() {
return null;
}
}
所有的新書都使用 NewBook
這個設計就違反了 ISP 原則,Book
強制所有的書都必須有收購資訊和鑑定資訊,但新書卻並沒有這兩項,將新書實現 Book
介面強制新書也必須要有這兩項資訊,無奈只能使用折中辦法返回null。
要改變這種情況,我們需要將收購資訊和鑑定資訊單獨拆到一個介面中,二手書的實現繼承這個介面,而新書的實現不繼承這兩個介面。
public interface Book {
public String isbn();
public String author();
public Date publishDate();
public PublisherInfo publisher();
public PurchaseInfo purchaseInfo();
public IdentificationInfo identificationInfo();
}
public interface SecondHand {
public PurchaseInfo purchaseInfo();
public IdentificationInfo identificationInfo();
}
介面拆分成這樣已經滿足網站的要求了,如果後面網站發展越來越大,鑑定成本不可承受時,有些書籍不作鑑定直接入庫,這時我們就需要將 SecondHand
介面再拆分成兩個介面,將收購資訊和鑑定資訊分離開來,不作耦和。
與 SRP 的比較
SRP 原則說的是一個類只能有一個改變的理由,ISP 原則指的是使用方不應該依賴它不使用的方法。有的設計符合 SRP 原則卻並不符合 ISP 原則。
舉一個例子,正常的 Stack
都有 push
pop
方法,如果使用方有一個使用場景,只能使用 push
, 不能使用 pop
, 那麼使用方就不能繼承 Stack
來實現自己的功能,與 ISP 原則相悖。但是原始的 Stack
設計是完全符合 SRP 原則的,push
與 pop
就是它自己的職責。
從這個例子可以看出,ISP 原則不僅僅能指導我們分離介面,還能幫助判斷一個類的繼承是不是合理的。
可能有的人覺得這個例子牽強,誰會限制一個 Stack
不能有 pop
方法。大家可以去看下Java 中的 Stack
實現,它繼承了Vector
,而 Vector
是一個 List
, Stack
應該只能壓入彈出的,但是卻繼承了 List
的 add
,remove
,get
等方法,是一個很糟糕的實現設計。
總結
介面分離原則與單一職責原則挺相近,但在某些點上是有區別的。日常編碼實現某個介面、繼承某個類時,問問自己,這樣符合 ISP 原則麼?