設計模式六大原則(四)----接口隔離原則


一. 接口隔離原則的定義

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM