JAVA基礎:怎樣設計合適的介面(轉)
JAVA基礎:怎樣設計合適的介面(轉)[@more@]摘要:我們在設計系統介面時,經常會遇到這樣的問題:
我們的介面應該提供多少方法才合適?
我們的介面應該提供"原子方法"還是"複合方法"?
我們的介面是否應該封裝(或者,能否封裝)所有的細節?
介面的設計需要考慮使用者的使用習慣、使用的方便程度、使用的安全程度,根據我的程式設計經驗,下面會詳細討論介面設計的2個需要權衡的方面:介面的單一化 & 複合化。
介面
介面提供了不同系統之間或者系統不同元件之間的界定。在軟體中,介面提供了一個屏障,從而從實現中分離目標,從具體中分離抽象,從作者中分離使用者。
站在使用者的角度看,一個介面建立並命名了一個目標物件的使用方法。一些約束(例如:編譯時的型別系統、執行時的異常機制及返回值)使得類作者的目的得以體現和加強。供給(affordances)指事物的被感知的真實的屬性,這些屬性可以決定事物使用的可能方法,供給提供了對事物操作的線索。
類設計者的一個職責便是在介面中減小約束與供給之間的隔閡、匹配目標以及一定程度上的自由度,儘可能減小錯誤使用目標物件的可能。
封裝
對於封裝來說,遠不止資料私有那麼簡單。在設計中,封裝往往會涉及到自我包含(self-containment)。如果一個類需要你知道如何呼叫它方法(e.g. 在一個執行緒的環境中,在一個方法呼叫後呼叫另一個方法,你必須明確地同步物件),那麼它的封裝性就不如將所有這些全部包含並隱藏的類(e.g. 這個類是thread-safe的)好。前一個設計存在著設計的漏洞,它的許多限定條件是模糊的,而且把部分責任推給了使用者,而不是讓類提供者做這些工作來完成類的設計。
在空間或者時間上分離方法的執行(例如,執行緒,遠端方法呼叫,訊息佇列),能夠對設計的正確性和效率產生意義深遠的影響。這種分離帶來的結果是不可忽視的:
併發引入了不確定性和環境(context)選擇的開銷;
分佈引入了回撥的開銷,這些開銷可能不斷增加,而且會導致錯誤。
這些是設計的問題,修改它們可不是象修改bug那樣簡單。
如果一個介面主要由存取方法(set和get方法)組成,每個方法都相應的直接指向某個私有域,那麼它的封裝性會很差。介面中的域存取方法通常是不會提供資訊的:他們在物件的使用中不能通訊、簡單化和抽象化,這通常會導致程式碼冗長,並且容易出錯。
所以,我們首先考慮介面設計的第一個原則:
命令與查詢分離(Command-Query Separation)
要求:保證一個方法不是命令(Command)就是查詢(Query)
定義:
查詢:當一個方法返回一個值來回應一個問題的時候,它就具有查詢的性質;
命令:當一個方法要改變物件的狀態的時候,它就具有命令的性質;
通常,一個方法可能是純的Command模式或者是純的Query模式,或者是兩者的混合體。在設計介面時,如果可能,應該儘量使介面單一化,保證方法的行為嚴格的是命令或者是查詢,這樣查詢方法不會改變物件的狀態,沒有副作用(side effects),而會改變物件的狀態的方法不可能有返回值。也就是說:如果我們要問一個問題,那麼就不應該影響到它的答案。實際應用,要視具體情況而定,語義的清晰性和使用的簡單性之間需要權衡。
例如,在java.util.Iterator中,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 = collection.iterator();while(iterator.hasNext();){Object current = iterator.next();... use current...}
下面,我們考慮介面設計的第二個原則:
組合方法(Combined Method)
組合方法經常線上程和分佈環境中使用,來保證正確性並改善效率。
一些介面提供大量的方法,起初,這些方法看來是最小化的,而且相關性強。然而,在使用的過程中,一些介面顯現得過於原始,它們過於簡單化,從而迫使類使用者用更多的工作來實現普通的任務,並且,方法之間的先後順序及依賴性比較強(即,暫時耦合)。這導致了程式碼重複,而且非常麻煩和容易出錯。
一些需要同時執行成功的方法,在多執行緒、異常、和分佈的情況下會遇到麻煩。如果兩個動作需要同時執行,它們由兩個獨立的方法進行描述,必須都完全成功的執行,否則會導致所有動作的回滾。
執行緒的引入使這種不確定性大大增加。一系列方法同時呼叫一個易變的(mutable)物件,如果這個物件線上程之間共享,即使我們假設單獨的方法是執行緒安全的,也無法確保結果是意料之中的。看下面對Event Source的介面,它允許安置控制程式碼和對事件的查詢:
interface EventSource{Handler getHandler(Event event);void installHandler(Event event, Handler newHandler);}
執行緒之間的交叉呼叫可能會引起意想不到的結果。假設source域引用一個執行緒共享的物件,物件很可能在1、2之間被另一個執行緒安裝了一個新的控制程式碼:
class EventSourceExample{public void example(Event event, Handler newHandler){oldHandler = eventSource.getHandler(event); // 1//物件很可能在這裡被另一個執行緒安裝了一個新的控制程式碼eventSource.installHandler(event, newHandler); // 2}private EventSource eventSource;private Handler oldHandler;}
為了解決問題,也需要由類的使用者而不是類的設計者來完成:
class EventSourceExample{public void example(Event event, Handler newHandler){synchronized(eventSource){oldHandler = eventSource.getHandler(event);eventSource.installHandler(event, newHandler);}}private EventSource eventSource;private Handler oldHandler;}
我們假設:目標物件eventSource是遠端的,執行每一個方法體的時間和通訊的延遲相比是很短的。在這個例子中,eventSource的方法被呼叫了兩次,並可能在其他的例項中重複多次,因而,開銷也是至少兩倍。
此外還有一個問題是對外部的synchronized同步塊的使用需求。對synchronized塊的使用之所以會失敗,主要因為我們透過代理物件來完成工作,所以,呼叫者的synchronized塊,同步的是代理物件而不是最終的目標物件,呼叫者不可能對其行為做太多的保證。
Combined Method必須在分佈的環境,或者,執行緒環境中同時執行。它反映了使用者直接的應用,恢復策略和一些笨拙的方法被封裝到Combined Method中,並簡化了介面,減少了介面中不需要的累贅。Combined Method的效果是支援一種更像事務處理風格的設計。
在一個組合的Command-Query中提供一個單獨的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 = eventSource.installHandler(event, newHandler);}private EventSource eventSource;private Handler oldHandler;}
這樣,我們給呼叫者提供了一個更加安全的介面,並且不再需要他們解決執行緒的問題。從而降低了風險和程式碼量,將類設計的職責全部給了類設計者而不是推給使用者,即使有代理物件的出現也不會影響到正確性。
一個Combined Method可以是許多Query的集合,許多Command的集合,或者兩者兼有。這樣,它可能補充Command、Query方法,也可能與之相牴觸。當衝突發生的時候,優先選擇Combined Method會產生一個不同的正確性和適用性。
在另一個例子中,我們考慮獲得資源的情況。假設,在下面的介面中,方法acquire在資源可用前阻塞:
interface Resource{boolean isAcquired();void acquire();void release();}
類似於下面的程式碼會在一個執行緒系統中推薦使用:
class ResourceExample{public void example(){boolean acquired = false;synchronized(resource){if(!resource.isAcquired())resource.acquire();elseacquired = true;}if(!acquired)...}private Resource resource;}
然而,即使我們放棄可讀性和易用性,這樣的設計也不是一個Command-Query分離的設計。如果引入了代理,它就會失敗:
class ActualResource implements Resource {...}class ResourceProxy implements Resource {...}
如果使用者既可以透過ActualResource來完成工作,也可以透過ResourceProxy來完成工作,而且,ActualResource和ResourceProxy都沒有處理同步,則synchronized塊可能會失敗。因為,既然我們可以透過代理物件ResourceProxy來完成工作,那麼,呼叫者的synchronized塊,同步的就是代理物件ResourceProxy而不是最終的目標物件ActualResource。
一個Combined Method解決了這個問題,它使併發和間接性更加透明。
interface Resource{
boolean tryAcquire();
}
下面的程式碼清晰、簡單並且正確:
class ResourceExample{public void example(){if(!resource.tryAcquire())...}private Resource resource;}
Combined Method帶來的一個結果是使一些測試和基於斷言的程式設計變得十分笨拙,然而,它適合解決執行緒和分佈問題。
實際應用中,介面應該單一化還是複合化,要視具體情況而定。
我們的介面應該提供多少方法才合適?
我們的介面應該提供"原子方法"還是"複合方法"?
我們的介面是否應該封裝(或者,能否封裝)所有的細節?
介面的設計需要考慮使用者的使用習慣、使用的方便程度、使用的安全程度,根據我的程式設計經驗,下面會詳細討論介面設計的2個需要權衡的方面:介面的單一化 & 複合化。
介面
介面提供了不同系統之間或者系統不同元件之間的界定。在軟體中,介面提供了一個屏障,從而從實現中分離目標,從具體中分離抽象,從作者中分離使用者。
站在使用者的角度看,一個介面建立並命名了一個目標物件的使用方法。一些約束(例如:編譯時的型別系統、執行時的異常機制及返回值)使得類作者的目的得以體現和加強。供給(affordances)指事物的被感知的真實的屬性,這些屬性可以決定事物使用的可能方法,供給提供了對事物操作的線索。
類設計者的一個職責便是在介面中減小約束與供給之間的隔閡、匹配目標以及一定程度上的自由度,儘可能減小錯誤使用目標物件的可能。
封裝
對於封裝來說,遠不止資料私有那麼簡單。在設計中,封裝往往會涉及到自我包含(self-containment)。如果一個類需要你知道如何呼叫它方法(e.g. 在一個執行緒的環境中,在一個方法呼叫後呼叫另一個方法,你必須明確地同步物件),那麼它的封裝性就不如將所有這些全部包含並隱藏的類(e.g. 這個類是thread-safe的)好。前一個設計存在著設計的漏洞,它的許多限定條件是模糊的,而且把部分責任推給了使用者,而不是讓類提供者做這些工作來完成類的設計。
在空間或者時間上分離方法的執行(例如,執行緒,遠端方法呼叫,訊息佇列),能夠對設計的正確性和效率產生意義深遠的影響。這種分離帶來的結果是不可忽視的:
併發引入了不確定性和環境(context)選擇的開銷;
分佈引入了回撥的開銷,這些開銷可能不斷增加,而且會導致錯誤。
這些是設計的問題,修改它們可不是象修改bug那樣簡單。
如果一個介面主要由存取方法(set和get方法)組成,每個方法都相應的直接指向某個私有域,那麼它的封裝性會很差。介面中的域存取方法通常是不會提供資訊的:他們在物件的使用中不能通訊、簡單化和抽象化,這通常會導致程式碼冗長,並且容易出錯。
所以,我們首先考慮介面設計的第一個原則:
命令與查詢分離(Command-Query Separation)
要求:保證一個方法不是命令(Command)就是查詢(Query)
定義:
查詢:當一個方法返回一個值來回應一個問題的時候,它就具有查詢的性質;
命令:當一個方法要改變物件的狀態的時候,它就具有命令的性質;
通常,一個方法可能是純的Command模式或者是純的Query模式,或者是兩者的混合體。在設計介面時,如果可能,應該儘量使介面單一化,保證方法的行為嚴格的是命令或者是查詢,這樣查詢方法不會改變物件的狀態,沒有副作用(side effects),而會改變物件的狀態的方法不可能有返回值。也就是說:如果我們要問一個問題,那麼就不應該影響到它的答案。實際應用,要視具體情況而定,語義的清晰性和使用的簡單性之間需要權衡。
例如,在java.util.Iterator中,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 = collection.iterator();while(iterator.hasNext();){Object current = iterator.next();... use current...}
下面,我們考慮介面設計的第二個原則:
組合方法(Combined Method)
組合方法經常線上程和分佈環境中使用,來保證正確性並改善效率。
一些介面提供大量的方法,起初,這些方法看來是最小化的,而且相關性強。然而,在使用的過程中,一些介面顯現得過於原始,它們過於簡單化,從而迫使類使用者用更多的工作來實現普通的任務,並且,方法之間的先後順序及依賴性比較強(即,暫時耦合)。這導致了程式碼重複,而且非常麻煩和容易出錯。
一些需要同時執行成功的方法,在多執行緒、異常、和分佈的情況下會遇到麻煩。如果兩個動作需要同時執行,它們由兩個獨立的方法進行描述,必須都完全成功的執行,否則會導致所有動作的回滾。
執行緒的引入使這種不確定性大大增加。一系列方法同時呼叫一個易變的(mutable)物件,如果這個物件線上程之間共享,即使我們假設單獨的方法是執行緒安全的,也無法確保結果是意料之中的。看下面對Event Source的介面,它允許安置控制程式碼和對事件的查詢:
interface EventSource{Handler getHandler(Event event);void installHandler(Event event, Handler newHandler);}
執行緒之間的交叉呼叫可能會引起意想不到的結果。假設source域引用一個執行緒共享的物件,物件很可能在1、2之間被另一個執行緒安裝了一個新的控制程式碼:
class EventSourceExample{public void example(Event event, Handler newHandler){oldHandler = eventSource.getHandler(event); // 1//物件很可能在這裡被另一個執行緒安裝了一個新的控制程式碼eventSource.installHandler(event, newHandler); // 2}private EventSource eventSource;private Handler oldHandler;}
為了解決問題,也需要由類的使用者而不是類的設計者來完成:
class EventSourceExample{public void example(Event event, Handler newHandler){synchronized(eventSource){oldHandler = eventSource.getHandler(event);eventSource.installHandler(event, newHandler);}}private EventSource eventSource;private Handler oldHandler;}
我們假設:目標物件eventSource是遠端的,執行每一個方法體的時間和通訊的延遲相比是很短的。在這個例子中,eventSource的方法被呼叫了兩次,並可能在其他的例項中重複多次,因而,開銷也是至少兩倍。
此外還有一個問題是對外部的synchronized同步塊的使用需求。對synchronized塊的使用之所以會失敗,主要因為我們透過代理物件來完成工作,所以,呼叫者的synchronized塊,同步的是代理物件而不是最終的目標物件,呼叫者不可能對其行為做太多的保證。
Combined Method必須在分佈的環境,或者,執行緒環境中同時執行。它反映了使用者直接的應用,恢復策略和一些笨拙的方法被封裝到Combined Method中,並簡化了介面,減少了介面中不需要的累贅。Combined Method的效果是支援一種更像事務處理風格的設計。
在一個組合的Command-Query中提供一個單獨的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 = eventSource.installHandler(event, newHandler);}private EventSource eventSource;private Handler oldHandler;}
這樣,我們給呼叫者提供了一個更加安全的介面,並且不再需要他們解決執行緒的問題。從而降低了風險和程式碼量,將類設計的職責全部給了類設計者而不是推給使用者,即使有代理物件的出現也不會影響到正確性。
一個Combined Method可以是許多Query的集合,許多Command的集合,或者兩者兼有。這樣,它可能補充Command、Query方法,也可能與之相牴觸。當衝突發生的時候,優先選擇Combined Method會產生一個不同的正確性和適用性。
在另一個例子中,我們考慮獲得資源的情況。假設,在下面的介面中,方法acquire在資源可用前阻塞:
interface Resource{boolean isAcquired();void acquire();void release();}
類似於下面的程式碼會在一個執行緒系統中推薦使用:
class ResourceExample{public void example(){boolean acquired = false;synchronized(resource){if(!resource.isAcquired())resource.acquire();elseacquired = true;}if(!acquired)...}private Resource resource;}
然而,即使我們放棄可讀性和易用性,這樣的設計也不是一個Command-Query分離的設計。如果引入了代理,它就會失敗:
class ActualResource implements Resource {...}class ResourceProxy implements Resource {...}
如果使用者既可以透過ActualResource來完成工作,也可以透過ResourceProxy來完成工作,而且,ActualResource和ResourceProxy都沒有處理同步,則synchronized塊可能會失敗。因為,既然我們可以透過代理物件ResourceProxy來完成工作,那麼,呼叫者的synchronized塊,同步的就是代理物件ResourceProxy而不是最終的目標物件ActualResource。
一個Combined Method解決了這個問題,它使併發和間接性更加透明。
interface Resource{
boolean tryAcquire();
}
下面的程式碼清晰、簡單並且正確:
class ResourceExample{public void example(){if(!resource.tryAcquire())...}private Resource resource;}
Combined Method帶來的一個結果是使一些測試和基於斷言的程式設計變得十分笨拙,然而,它適合解決執行緒和分佈問題。
實際應用中,介面應該單一化還是複合化,要視具體情況而定。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10617731/viewspace-959467/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 什麼樣的人適合學UI設計?UI
- 《java程式設計基礎》java的基礎知識(三)Java程式設計
- Java 基礎02Java程式設計基礎Java程式設計
- java基礎-介面Java
- java 設計模式基礎Java設計模式
- 為什麼說沒有程式設計基礎的小白學Python最合適?程式設計Python
- 怎樣找到適合自己的產品呢
- java基礎:CompletionStage介面Java
- 這本最適合夯實基礎的經典 Java 書籍,可能 80% 的 Java 程式設計師沒認真看過!Java程式設計師
- java初學者(零基礎如何入門),我適不適合學習JAVA?Java
- Python適合不適合零基礎學習呢?Python
- 適合零基礎初學者學習的Java順序Java
- Java、Python以及大資料哪個適合0基礎?JavaPython大資料
- 零基礎參加Java培訓班合適嗎Java
- 程式設計必備基礎 計算機組成原理+作業系統+計算機網路,計算機基礎——更適合程式設計師的程式設計必備基礎知識作業系統計算機網路程式設計師
- Java基礎篇--設計模式Java設計模式
- Android基礎及應用 介面設計Android
- 2021年Java發展前景怎麼樣?還適合入行嗎?Java
- 最適合Java基礎練手的Java小專案「圖書管理系統」Java
- 自適應介面設計
- 怎樣選擇適合自己php框架PHP框架
- Java基礎視訊教程(最適合初學者入門)Java
- Java、Python以及大資料哪個適合0基礎學?JavaPython大資料
- 《java程式設計基礎》方法的過載Java程式設計
- Java程式設計基礎33——JDBCJava程式設計JDBC
- JAVA網路程式設計基礎Java程式設計
- Java 基礎程式設計筆記Java程式設計筆記
- Java併發程式設計基礎Java程式設計
- 適合高階Java程式設計師看的10本書Java程式設計師
- 適合新手的Java程式設計課程訓練網址Java程式設計
- [適合小白的Linu基礎入門教程一
- Java基礎06 組合Java
- 怎樣選擇合適的協作辦公工具
- 轉賬介面設計
- 全網最適合入門的物件導向程式設計教程:50 Python函式方法與介面-介面和抽象基類物件程式設計Python函式抽象
- Java基礎-抽象類和介面Java抽象
- Java基礎05 實施介面Java
- Java基礎之淺談介面Java
- 《java程式設計基礎》例題5.6Java程式設計