設計模式六大原則(四)----介面隔離原則

盛開的太陽發表於2021-06-07

一. 介面隔離原則的定義

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. 將臃腫龐大的介面分解為多個粒度小的介面,可以預防外來變更的擴散,提高系統的靈活性和可維護性。
  2. 介面隔離提高了系統的內聚性,減少了對外互動,降低了系統的耦合性。
  3. 如果介面的粒度大小定義合理,能夠保證系統的穩定性;然而,如果定義過小,則會造成介面數量過多,使設計複雜化;如果定義太大,靈活性降低,無法提供定製服務,給整體專案帶來無法預料的風險。
  4. 使用多個專門的介面能夠體現物件的層次,因為可以通過介面的繼承,實現對總介面的定義。
  5. 能減少專案工程中的程式碼冗餘。過大的大介面裡面通常放置許多不用的方法,當實現這個介面的時候,被迫設計冗餘的程式碼。

四. 介面隔離原則的實現方法

在具體應用介面隔離原則時,應該根據以下幾個規則來衡量。
1)介面要儘量小
不能出現Fat Interface;但是要有限度,首先不能違反單一職責原則(不能一個介面對應半個職責)。

2)介面要高內聚
在介面中儘量少公佈public方法。
介面是對外的承諾,承諾越少對系統的開發越有利。

3)定製服務
只提供訪問者需要的方法。例如,為管理員提供IComplexSearcher介面,為公網提供ISimpleSearcher介面。

4)介面的設計是有限度的
瞭解環境,拒絕盲從。每個專案或產品都有選定的環境因素,環境不同,介面拆分的標準就不同, 需要深入瞭解業務邏輯。

五. 介面隔離原則的建議

  1. 一個介面只服務於一個子模組或業務邏輯;
  2. 通過業務邏輯壓縮介面中的public方法;
  3. 已被汙染了的介面,儘量去修改;若變更的風險較大,則採用介面卡模式轉化處理;
  4. 拒絕盲從

五. 案例分析

下面以學生成績管理為例來說明介面隔離原則:

分析:學生成績管理程式一般包含查詢成績、新增成績、刪除成績、修改成績、計算總分、計算平均分、列印成績資訊等功能,通常我們會怎麼做呢?

一: 最初的設計

通常我們設計介面的方式如下:

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 所示

相關文章