天天看點

寂然解讀設計模式 - 接口隔離原則

I walk very slowly, but I never walk backwards            

設計模式原則 - 接口隔離原則

寂然

大家好,我是寂然~,本節課呢,我來給大家介紹設計模式原則之接口隔離原則,話不多說,我們直接進入正題,老規矩,首先帶大家了解一下接口隔離原則的官方定義,并作一個解釋,然後我們通過案例代碼來具體分析

官方定義

接口隔離原則(Interface Segregation Principle),又稱為ISP原則,官方定義為:

  1. Clients should not be forced to depend upon interfaces that they don’t use. (用戶端不應該依賴它不需要的接口)
  2. The dependency of one class to another one should depend on the smallest possible interface. (類間的依賴關系應該建立在最小的接口上)

基本介紹

其實通俗來了解就是,不要在一個接口裡面放很多的方法,這樣會顯得這個類很臃腫不堪。接口應該盡量細化,一個接口對應一個功能子產品,同時接口裡面的方法應該盡可能的少,使接口更加輕便靈活。

案例示範 - 市場風雲

為了讓大家更好的了解接口隔離原則,我們通過一個案例來詳細解析

假設有這樣一個案例場景,現在有一個接口knife,給定他有三個能力,可以切蘋果,切番茄,切洋芋,兩個類張廚師,李廚師分别具有這些能力,有一個水果店類,假設是需要張師傅來切蘋果和切番茄,而另一個蔬菜店類需要李師傅來切番茄和切洋芋

OK,明确了需求之後,那相關的基本定義代碼如下圖所示

// 定義接口knife
interface Knife {
    //切蘋果的能力
    void cutApple();

    //切番茄的能力
    void cutTomato();

    //切洋芋的能力
    void cutPotato();

}

//張廚師類
class CookZhang implements Knife {

    @Override
    public void cutApple() {
        System.out.println("張廚師正在切蘋果");
    }

    @Override
    public void cutTomato() {
        System.out.println("張廚師正在切番茄");
    }

    @Override
    public void cutPotato() {
        System.out.println("張廚師正在切洋芋");
    }
}

//李廚師類
class CookLi implements Knife {

    @Override
    public void cutApple() {
        System.out.println("李廚師正在切蘋果");
    }

    @Override
    public void cutTomato() {
        System.out.println("李廚師正在切番茄");
    }

    @Override
    public void cutPotato() {
        System.out.println("李廚師正在切洋芋");
    }
}           

上面這段代碼相信大家并不陌生,将基本的定義好之後,那我們接着往下走,既然有一個水果店類,假設是需要張師傅來切蘋果和切番茄,蔬菜店類需要李師傅來切番茄和切洋芋,那我們可以是這樣一個思路,在水果店類裡,定義方法,将接口類型作為參數傳入,調用方法則傳入接口的實作類張廚師類,即水果店類通過接口來依賴或者說使用張廚師類,進而達到上面的需求,OK,按照這個思路,相應代碼如下圖所示

//水果店類
class FruitShop {
    // 将接口類型作為參數傳入
    public void cutApple(Knife knife) {
        knife.cutApple(); 
    }

    public void cutTomato(Knife knife) {
        knife.cutTomato();
    }
}

//蔬菜店類
class VegetableShop {

    public void cutTomato(Knife knife) {
        knife.cutTomato();
    }

    public void cutPotato(Knife knife) {
        knife.cutPotato();
    }
}

//簡易測試代碼示例
public class SegregationDemo {
    public static void main(String[] args) {

        new FruitShop().cutApple(new Cookzhang());   
        //列印結果:張廚師正在切蘋果

        new VegetableShop().cutPotato(new CookLi());    
        //列印結果:李廚師正在切洋芋
    }
}           

案例分析

OK,我們用上面的代碼實作了這個簡易案例,那接下來,我們就來分析一下,為了便于大家了解,我把對應的UML類圖展示出來,如下圖所示、當然,有關UML類圖詳細的知識會在後面和大家一一見面,不要着急哈

寂然解讀設計模式 - 接口隔離原則

OK,那麼大家來看上面的類圖,現在需求裡,類FruitShop通過接口Kinfe會依賴或者說使用CookZhang,但是FruitShop裡面卻隻會用到CookZhang其中的兩個方法,同樣,類VegetableShop也是如此,換句話說,廚師CookZhang 業務上需要兩種切割的能力,就要實作接口 Kinfe,但是Kinfe對于CookZhang來說不是最小接口,那麼類 CookZhang 就必須去實作他們不需要的方法,對吧,上面的案例中 CookZhang 雖然用不到,但是也實作了切洋芋的方法,同樣對于CookLi而言,也是這樣

那麼這時,大家回顧一下上面提到的接口隔離原則的定義,類間的依賴關系應該建立在最小的接口上,顯然,上面的寫法雖然可以實作需求,但是Kinfe對于CookZhang來說不是最小接口,違反了接口隔離原則

解決方案

那到這裡有小夥伴就會問了,既然上面的情況違反了接口隔離原則,那按照隔離原則,代碼上應當怎樣處理呢?

那既然接口隔離原則建議 類間的依賴關系應該建立在最小的接口上,那我們不妨将接口 Kinfe 拆分為獨立的幾個接口(這裡我們拆分成 3 個接口),類 FruitShop 和類 VegetableShop 分别與他們需要的接口建立依賴關系

按照這種思路,代碼示例如下圖所示:

// 将Knife拆分為三個接口
interface AppleKnife {
    //切蘋果的能力
    void cutApple();
}

interface TomatoKnife {
    //切番茄的能力
    void cutTomato();
}

interface PotatoKnife {
    //切洋芋的能力
    void cutPotato();
}

//張廚師類
class CookZhang implements AppleKnife,TomatoKnife {

    @Override
    public void cutApple() {
        System.out.println("張廚師正在切蘋果");
    }

    @Override
    public void cutTomato() {
        System.out.println("張廚師正在切番茄");
    }
}

//李廚師類
class CookLi implements PotatoKnife,TomatoKnife {

    @Override
    public void cutTomato() {
        System.out.println("李廚師正在切番茄");
    }

    @Override
    public void cutPotato() {
        System.out.println("李廚師正在切洋芋");
    }
}           

按照第二種方案,我們将接口 Kinfe拆分為三個接口,張廚師需要切蘋果和切番茄兩種能力,就實作AppleKnife,TomatoKnife兩個接口即可,同樣,水果店的代碼示例如下:

//水果店類
class FruitShop {

    public void cutApple(AppleKnife knife) {
        knife.cutApple();
    }

    public void cutTomato(TomatoKnife knife) {
        knife.cutTomato();
    }
}

//蔬菜店類
class VegetableShop {

    public void cutTomato(TomatoKnife knife) {
        knife.cutTomato();
    }

    public void cutPotato(PotatoKnife knife) {
        knife.cutPotato();
    }

}           

解決方案對應的UML類圖,如下圖所示:

寂然解讀設計模式 - 接口隔離原則

案例總結

OK,那我們的代碼改造完成,現行的代碼就符合了接口隔離原則,經過這個案例,其實說白了,接口隔離原則就是當我一個類通過接口依賴(使用)另一個類的時候,要保證依賴的該接口是最小的,接口裡面有方法用不到的,就進行隔離,而隔離的做法就是,就對原來接口進行拆分,拆分為最小粒度,來避免耦合

接口隔離原則相對來說是比較簡單的,但是呢,要給大家點出來,其實很多小夥伴實際開發中,所寫的代碼或多或少都符合設計原則而不自知,現在一條一條明确出來,後面在寫代碼的過程中小夥伴們就會有意的去遵守這些看似簡單卻很重要的設計模式原則,進而降低耦合,去提高代碼的品質

與單一職責原則對比

或許看到接口隔離原則這樣的定義很多人會覺得和單一職責原則很像,但是這兩個原則還是有着很鮮明的差別,接口隔離原則和單一職責原則的審視角度是不同的,單一職責原則要求類和接口職責單一,注重的是職責,是業務邏輯上的劃分,而接口隔離原則要求方法要盡可能的少,是在接口設計上的考慮

例如一個接口的職責包含10個方法,這10個方法都放在一個接口中,并且提供給多個子產品通路,各個子產品按照規定的權限來通路,并規定了“不使用的方法不能通路”,這樣的設計是不符合接口隔離原則的,接口隔離原則要求“盡量使用多個專門的接口”,這裡專門的接口就是指提供給每個子產品的都應該是單一接口(即每一個子產品對應一個接口),而不是建立一個龐大臃腫的接口來容納所有的用戶端通路

當然,根據接口隔離原則拆分接口時,首先必須滿足單一職責原則

下節預告

OK,下一節,我們正式進入設計模式原則之依賴倒轉原則的學習,我會為大家用多個案例分析,來解讀設計模式原則之依賴倒轉原則,以及它的注意事項和細節,最後,希望大家在學習的過程中,能夠感覺到設計模式的有趣之處,高效而愉快的學習,那我們下期見~

繼續閱讀