熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java核心技術 >> 正文

怎樣設計合適的接口

2022-06-13   來源: Java核心技術 

  摘要我們在設計系統接口時經常會遇到這樣的問題
  我們的接口應該提供多少方法才合適?
  我們的接口應該提供原子方法還是復合方法
  我們的接口是否應該封裝(或者能否封裝)所有的細節?
  接口的設計需要考慮用戶的使用習慣使用的方便程度使用的安全程度根據我的編程經驗下面會詳細討論接口設計的個需要權衡的方面接口的單一化 & 復合化
  接口
  接口提供了不同系統之間或者系統不同組件之間的界定在軟件中接口提供了一個屏障從而從實現中分離目標從具體中分離抽象從作者中分離用戶
  
  站在用戶的角度看一個接口建立並命名了一個目標對象的使用方法一些約束(例如編譯時的類型系統運行時的異常機制及返回值)使得類作者的目的得以體現和加強供給(affordances)指事物的被感知的真實的屬性這些屬性可以決定事物使用的可能方法供給提供了對事物操作的線索
  
  類設計者的一個職責便是在接口中減小約束與供給之間的隔閡匹配目標以及一定程度上的自由度盡可能減小錯誤使用目標對象的可能
  
  封裝
  對於封裝來說遠不止數據私有那麼簡單在設計中封裝往往會涉及到自我包含(selfcontainment)如果一個類需要你知道如何調用它方法(eg 在一個線程的環境中在一個方法調用後調用另一個方法你必須明確地同步對象)那麼它的封裝性就不如將所有這些全部包含並隱藏的類(eg 這個類是threadsafe的)好前一個設計存在著設計的漏洞它的許多限定條件是模糊的而且把部分責任推給了用戶而不是讓類提供者做這些工作來完成類的設計
  
  在空間或者時間上分離方法的執行(例如線程遠程方法調用消息隊列)能夠對設計的正確性和效率產生意義深遠的影響這種分離帶來的結果是不可忽視的
  
  並發引入了不確定性和環境(context)選擇的開銷
  分布引入了回調的開銷這些開銷可能不斷增加而且會導致錯誤
  這些是設計的問題修改它們可不是象修改bug那樣簡單
  
  如果一個接口主要由存取方法(set和get方法)組成每個方法都相應的直接指向某個私有域那麼它的封裝性會很差接口中的域存取方法通常是不會提供信息的他們在對象的使用中不能通訊簡單化和抽象化這通常會導致代碼冗長並且容易出錯
  
  所以我們首先考慮接口設計的第一個原則
  
  命令與查詢分離(CommandQuery Separation)
  要求保證一個方法不是命令(Command)就是查詢(Query)
  
  定義
    查詢當一個方法返回一個值來回應一個問題的時候它就具有查詢的性質
    命令當一個方法要改變對象的狀態的時候它就具有命令的性質
  
  通常一個方法可能是純的Command模式或者是純的Query模式或者是兩者的混合體在設計接口時如果可能應該盡量使接口單一化保證方法的行為嚴格的是命令或者是查詢這樣查詢方法不會改變對象的狀態沒有副作用(side effects)而會改變對象的狀態的方法不可能有返回值也就是說如果我們要問一個問題那麼就不應該影響到它的答案實際應用要視具體情況而定語義的清晰性和使用的簡單性之間需要權衡
  
  例如在javautilIterator中hasNext可以被看作一種查詢remove是一種命令next合並了命令和查詢
  public interface Iterator{
  boolean hasNext();
  Object next();
  void remove();
  }
  
  
  
  
  這裡如果不將一個Iterator對象的當前值向前到下一個的話就不能夠查詢一個Iterator對象如果沒有提供一個復合方法next我們將需要定義一系列的命令方法例如初始化(initialization)繼續(continuation)訪問(access)和前進(advance)它們雖然清晰定義了每個動作但是客戶代碼過於復雜
  for(initialization; continuation condition; advance){
   access for use
  }
  
  
  
  
  將Command和Query功能合並入一個方法方便了客戶的使用但是降低了清晰性而且可能不便於基於斷言的程序設計並且需要一個變量來保存查詢結果
  Iterator iterator = erator();
  while(iteratorhasNext();){
  Object current = iteratornext();
   use current
  }
  
  
  下面我們考慮接口設計的第二個原則
  
  組合方法(Combined Method)
  組合方法經常在線程和分布環境中使用來保證正確性並改善效率
  
  一些接口提供大量的方法起初這些方法看來是最小化的而且相關性強然而在使用的過程中一些接口顯現得過於原始它們過於簡單化從而迫使類用戶用更多的工作來實現普通的任務並且方法之間的先後順序及依賴性比較強(即暫時耦合)這導致了代碼重復而且非常麻煩和容易出錯
  
  一些需要同時執行成功的方法在多線程異常和分布的情況下會遇到麻煩如果兩個動作需要同時執行它們由兩個獨立的方法進行描述必須都完全成功的執行否則會導致所有動作的回滾
  
  線程的引入使這種不確定性大大增加一系列方法同時調用一個易變的(mutable)對象如果這個對象在線程之間共享即使我們假設單獨的方法是線程安全的也無法確保結果是意料之中的看下面對Event Source的接口它允許安置句柄和對事件的查詢
  interface EventSource{
  Handler getHandler(Event event);
  void installHandler(Event event Handler newHandler);
  }
  
  
  
  
  線程之間的交叉調用可能會引起意想不到的結果假設source域引用一個線程共享的對象對象很可能在之間被另一個線程安裝了一個新的句柄
  class EventSourceExample{
  public void example(Event event Handler newHandler){
  oldHandler = eventSourcegetHandler(event); //
  //對象很可能在這裡被另一個線程安裝了一個新的句柄
  eventSourceinstallHandler(event newHandler); //
  }
  private EventSource eventSource;
  private Handler oldHandler;
  }
  
  
  
  
  為了解決問題也需要由類的使用者而不是類的設計者來完成
  class EventSourceExample{
  public void example(Event event Handler newHandler){
  synchronized(eventSource){
  oldHandler = eventSourcegetHandler(event);
  eventSourceinstallHandler(event newHandler);
  }
  }
  private EventSource eventSource;
  private Handler oldHandler;
  }
  
  
  
  
  我們假設目標對象eventSource是遠程的執行每一個方法體的時間和通訊的延遲相比是很短的在這個例子中eventSource的方法被調用了兩次並可能在其他的實例中重復多次因而開銷也是至少兩倍
  
  此外還有一個問題是對外部的synchronized同步塊的使用需求對synchronized塊的使用之所以會失敗主要因為我們通過代理對象來完成工作所以調用者的synchronized塊同步的是代理對象而不是最終的目標對象調用者不可能對其行為做太多的保證
  
  Combined Method必須在分布的環境或者線程環境中同時執行它反映了用戶直接的應用恢復策略和一些笨拙的方法被封裝到Combined Method中並簡化了接口減少了接口中不需要的累贅Combined Method的效果是支持一種更像事務處理風格的設計
  
  在一個組合的CommandQuery中提供一個單獨的Query方法通常是合理的提供分離的Command方法是不太常見的因為Combined Method可以完成這一工作只要調用者簡單的忽略返回結果如果返回一個結果招致一個開銷的話才可能會提供一個單獨的Command方法
  
  回到前一個例子中如果installHandler method返回上一次安裝的句柄則設計變得更加簡單和獨立
  interface EventSource{
  Handler installHandler(Event event Handler newHandler);
  }
  客戶代碼如下
  class EventSourceExample{
  public void example(Event event Handler newHandler){
  oldHandler = eventSourceinstallHandler(event newHandler);
  }
  private EventSource eventSource;
  private Handler oldHandler;
  }
  
  
  
  這樣我們給調用者提供了一個更加安全的接口並且不再需要他們解決線程的問題從而降低了風險和代碼量將類設計的職責全部給了類設計者而不是推給用戶即使有代理對象的出現也不會影響到正確性
  
  一個Combined Method可以是許多Query的集合許多Command的集合或者兩者兼有這樣它可能補充CommandQuery方法也可能與之相抵觸當沖突發生的時候優先選擇Combined Method會產生一個不同的正確性和適用性
  
  在另一個例子中我們考慮獲得資源的情況假設在下面的接口中方法acquire在資源可用前阻塞
  interface Resource{
  boolean isAcquired();
  void acquire();
  void release();
  }
  
  
  
  類似於下面的代碼會在一個線程系統中推薦使用
  class ResourceExample{
  public void example(){
  bool
From:http://tw.wingwit.com/Article/program/Java/hx/201311/26294.html
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.