1. 程式人生 > >設計原則之介面分離原則(ISP)

設計原則之介面分離原則(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 原則的,pushpop 就是它自己的職責。

從這個例子可以看出,ISP 原則不僅僅能指導我們分離介面,還能幫助判斷一個類的繼承是不是合理的。

可能有的人覺得這個例子牽強,誰會限制一個 Stack 不能有 pop 方法。大家可以去看下Java 中的 Stack 實現,它繼承了Vector ,而 Vector 是一個 ListStack 應該只能壓入彈出的,但是卻繼承了 Listadd,remove,get 等方法,是一個很糟糕的實現設計。

總結

介面分離原則與單一職責原則挺相近,但在某些點上是有區別的。日常編碼實現某個介面、繼承某個類時,問問自己,這樣符合 ISP 原則麼?

Reference

  1. Interface segregation principle
  2. Understand Single Responsibility and Interface Seg
  3. In SOLID, what is the distinction between SRP and ISP? (Single Responsibility Principle and Interface Segregation