面向對象設計 接口隔離(ISP)

ISP

主目錄:一個面向對象設計(OOD)的學習思路設計c++

引入:bash

老手機: 大家這些年輕手機光溜溜的,全身上下只有兩個插孔幾個按鈕,爲啥這麼受歡迎? 新手機:老前輩,您雖然佔了一半都是按鈕,能夠快速的點到,可是多數狀況下都沒用呀!我雖然只有幾個按鈕,但都是常常用到滴。我也能達到和你同樣的效果,並且更簡潔。 老手機:恩,人們只有打字的時候纔用到那些按鈕。 新手機:因此在日常時候,我這幾個按鈕就能夠知足大部分須要了。 老手機:真是一代比一代強咯!ide

ISP.png

1.何爲ISP?

  • 全稱:接口隔離原則(Interface Segregation Principle)
  • 定義:客戶程序不該該被迫依賴於它們不使用的方法

2.如何理解ISP?

  • 好比圖2-1中的鴕鳥類不該該被迫依賴於不使用的飛翔方法 學習

    2-1.違反了ISP

  • 如今將2-1的例子中的接口鳥進行拆分,能飛的鳥類麻雀實現接口飛鳥,不能飛的鳥類鴕鳥實現接口鳥,以下圖2-2所示。ui

2-2.知足ISP.png

  • 可能到這裏你們有個疑惑:接口變多了!對!就是接口變多了。不是上面還舉例了手機的例子嗎?闡明瞭減小接口的好處。
  • 其實咱們減小並非接口,而是接口中的抽象方法。
  • 經過分離來知足客戶端的需求,使客戶端程序中只存在須要的方法。
  • 客戶端的不一樣需求才是致使接口改變的緣由。

3.遵循ISP有什麼好處?

  • 不遵循ISP而致使的一些問題,在圖2-1中,鴕鳥是不須要飛的,但保留了飛的方法。this

  • 如今接口中的飛()方法須要進行改動,假如改爲:boolean fly()---能夠理解爲調用一次向上飛,再調用一次向下飛,依次循環。spa

  • 如今不只會飛的鳥須要改動,連鴕鳥這些不會飛的鳥都要莫名奇妙的跟着去改動。設計

  • 顯然這致使了程序之間的耦合加強,影響到了不該該影響的客戶程序code

  • 如今正過來看遵循ISP接口,如圖2-2所示的例子,分離了方法,使得更改時並不會影響到不相干的客戶程序(鴕鳥類)對象

  • 須要儘量避免這種耦合,所以咱們但願分離接口。

  • 能夠看出,分離接口有利於咱們對需求變動時的快速高效的執行行動。

  • 而且使之解構層次更加的分明

4.按部就班的例子(來自敏捷軟件開發[^foot1])

以ATM用戶界面爲例

  1. ATM的用戶界面有不一樣的交易模式,現將從ATM的基類Transaction(交易類)中派生子類:
  • DepositTransaction存款
  • WithdrawalTransaction取款
  • TransferTransaction轉帳
  1. 每個子類交易都有一個界面,所以要依賴於UI,調用的不一樣方法,如:DepositTransaction會調用UI類中的RequestDepositAmount()方法,當前ATM結果以下圖4-2-1所示。
    4-2-1.ATM操做解構
  • 這樣作是ISP告訴咱們應當避免的情形
  • 每一個操做使用的UI方法,其餘的操做都不會使用
  • 當每次Transaction子類的改動都會迫使對UI進行改動,從而影響到了其餘全部Transaction子類及其餘全部依賴於UI接口的類。
  • 當要增長一個支付煤氣費的交易時,爲了處理該操做想要顯示的特定消息,就須要在UI中加入新的方法。糟糕的是,因爲Transaction的子類所有依賴於UI接口,因此它們都須要從新編譯。
  1. 所以如今有一個辦法,將UI接口分解成像DepositUIWithdrawalUI以及TransferUI這樣的單獨接口,能夠避免這種不合適的耦合,最終的UI接口能夠去多重繼承這些單獨的接口。圖5-3-1和以後的代碼展現了這個模型。
    5-3-1.分離的ATM接口

定義交易接口

/** 存款UI接口*/
   interface DepositUI {
       void RequestDepositAmount();
   }

   /** 取款UI接口*/
   interface WithdrawalUI {
       void RequestWithdrawalAmount();
   }

   /** 轉帳UI接口*/
   interface TransferUI {
       void RequestTransferAmount();
   }

   /** UI接口繼承全部的交易接口*/
   interface UI extends DepositUI, WithdrawalUI, TransferUI{

   }
複製代碼

交易抽象類

/** 交易類*/
   abstract class Transaction {
       public abstract void Execute();
   }
複製代碼

交易派生類

/** 存款交易類*/
   class DepositTransaction extends Transaction {
       private DepositUI mDepositUI;
       public DepositTransaction(DepositUI mDepositUI) {
           this.mDepositUI = mDepositUI;
       }

       @Override
       public void Execute() {
           //...
           mDepositUI.RequestDepositAmount();
           //...
       }
   }

   /** 取款交易類*/
   class WithdrawalTransaction extends Transaction {
       private WithdrawalUI mWithdrawalUI;
       public WithdrawalTransaction(WithdrawalUI mWithdrawalUI) {
           this.mWithdrawalUI = mWithdrawalUI;
       }
       @Override
       public void Execute() {
           //...
           mWithdrawalUI.RequestWithdrawalAmount();
           //...
       }
   }

   /** 轉帳交易類*/
   class TransferTransaction extends Transaction {
       private TransferUI mTransferUI;
       public TransferTransaction(TransferUI mTransferUI) {
           this.mTransferUI = mTransferUI;
       }
       @Override
       public void Execute() {
           //...
           mTransferUI.RequestTransferAmount();
           //...
       }
   }
複製代碼

建立交易對象:因爲每一個操做都必須以特定的方式知曉UI版本,如TransferTransaction必須知道TransferUI。在程序中,使每一個操做的構造時給它傳入指向特定於它的UI的引用,從而解決這個問題。以下進行初始化

UI GUI;
   void fun() {
       DepositTransaction mDepositTransaction = new DepositTransaction(GUI);
   }
複製代碼

雖然這樣很方便,但一樣要求每一個操做都有一個指向對應UI的引用成員。另一種解決這個問題的方法是建立一組全局常量。全局變量並不老是意味着拙劣的設計,在這種狀況下,它們有着明顯的易於訪問的有點。

/** UI全局變量*/
class UIGlobals {
   public static DepositUI mDepositUI;
   public static WithdrawalUI mWithdrawalUI;
   public static TransferUI mTransferUI;
   public UIGlobals(UI lui) {
       UIGlobals.mDepositUI = lui;
       UIGlobals.mWithdrawalUI = lui;
       UIGlobals.mTransferUI = lui;
   }
}
複製代碼
/** 轉帳交易類*/
class TransferTransaction extends Transaction {
   @Override
   public void Execute() {
       //...
       UIGlobals.mTransferUI.RequestTransferAmount();
       //...
   }
}
複製代碼
/**
* UI的實現類
*/
class UIEntity implements UI {

   @Override
   public void RequestDepositAmount() {
       //...
   }

   @Override
   public void RequestTransferAmount() {
       //...
   }

   @Override
   public void RequestWithdrawalAmount() {
       //...
   }
}
複製代碼
/**
* 使用
*/
class A {
   //初始化UI靜態類
   UIGlobals mUIGlobals = new UIGlobals(new UIEntity());

   //調用姿式
   void fun() {
       Transaction mTransaction = new TransferTransaction();
       mTransaction.Execute();
   }
}
複製代碼

因爲敏捷軟件開發舉的例子是c++的,知識有限,表示不少看不懂,可能有些地方誤差較大,想了解更多建議親自去看看( ¯▽¯;)

5.總結

  • 胖類(fat class):就是上邊講解的不知足ISP的類型

  • 能夠看出胖類加強了類之間的耦合,使得對該胖類進行改動會影響到全部其餘類。

  • 經過將胖類接口分解成多個特定類(客戶端程序)的接口,使得強耦合得以解決

  • 而後該胖類繼承全部特定類的接口,並實現它們。就解除了這個特定類和它沒有調用方法間的依賴關係,並使得這些特定類之間互不依賴。

6.參考文獻[^foot2]

[^foot1]: 敏捷軟件開發 第12章 接口隔離原則(ISP) [^foot2]: 如何向妻子解釋OOD

相關文章
相關標籤/搜索