程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Java中的模式

Java中的模式

編輯:關於JAVA

世上一直有一個神話:設計可以並且應該獨立於實現的細節,設計通常被看作是一個抽象的概念而實現是一個代碼的具體實例。如果我們堅信"設計是一個富有創造性和目的性的活動:為某一個目標而精心制定的結構的概念,",一個結構如果不能夠說明它的環境,或者不能與環境協作,那麼這個結構就不適合這一目標。環境中包括目標平台--語言、工具、庫、中間件(middleware),等。還有它的功能性和非功能性的單元。

我們會認為在不知道地形布局的時候設計房屋,或者在不清楚使用的道材料的時候建造摩天大廈是不合理的事情。我們將線程、分布這類概念看作為小的編碼的細節的看法無疑是在設計中導致浪費精力(時間和金錢)的導火索,最終我們發現的是理論與實踐的差距在實踐中要比在理論中還大。雖然在一些情況下一個高層次設計的某部分可以在許多技術下保持不變,但是更多的情況是我們需要親自來補足這個圓圈,允許(甚至鼓勵)細節和實際的信息來影響並告知系統的結構。

模式(Patterns)的作用就是獲取這些結構上的信息。它們可以描述--預見性的或回顧性的--設計和設計的原理,講述從問題到解決,說明環境,獲取工作的動力以及應此產生的結果。這裡,我將集中講述兩個模式--Command-Query Separation和Command Method--為一個類接口中的方法分配任務,考察他們如何互相作用並影響並發的、分布的和有序的環境以及本地執行。

接口設計。顧名思義,接口提供了不同系統之間或者系統不同組件之間的界定。在軟件中,接口提供了一個屏障,從而從實現中分離了目標,從具體中分離了概念,從作者中分離了用戶。在Java中,有許多接口的概念:public部分為潛在的用戶提供了類和方法的接口,protected部分為它的子類(subclass)以及周圍的包提供了一個接口;一個包有一個公用的部分;反射(Reflection)是另外一種提供、使用對象方法接口的機制。

約束及供給。站在用戶對作者的角度,一個接口建立並命名了一個目的模型的使用方法。類接口中的方法提供了一種特殊的使用方法。是這些約束--編譯時的類型系統,運行是的異常機制及返回值--使得類作者的目的得以體現和加強。在這方面最簡單的例子是對封裝的意義的理解:私有化可以保證類用戶只可以通過類的公用方法接口來操作信息和行為。

然而,對於封裝來說,遠不止數據私有那麼簡單。在設計中,封裝往往會涉及到自我包含(self-containment)。一個需要你知道如何調用一個方法(e.g."在一個線程的環境中,在一個方法調用後調用另一個方法,你必須明確地同步對象")的類的封裝就不如將所有這些全部包含並隱藏的類(e.g."這個類是thread-safe的")好。前一個設計存在著設計的漏洞,它的許多限定條件是模糊的而不是經過加強的。這就把責任推給了用戶而不是讓類提供者做這些工作來完成類的設計,並且,這是不可避免的漏洞百出。

在這種情況下,供給(affordances)描述了使用的可行性和不可行性。

術語供給(affordances)指事物的被感知的真實的屬性,首先,這些屬性可以決定事物的使用的可能方法。一個椅子可以用來支撐其他東西,所以,可以坐人。一個椅子照樣可以搬運(carried)。玻璃可以透光,也可以被打碎……

供給提供了對事物操作的線索,板狀物可以壓、柄狀物可以旋轉,溝狀物可以插入東西。球狀物可以扔或者反彈。當使用了供給的優勢後,用戶可以只通過看便確定該做什麼:沒有圖、沒有標簽也沒有說明。復雜的事物可能會需要一些解釋,但是簡單的事物不應該這樣。當簡單的東西也需要用圖片、標簽來說明的時候,設計就是失敗的。

類設計者的一個職責便是在接口中減小約束與供給之間的隔閡(gap),匹配目標以及一定程度上的自由度,盡可能減小錯誤使用的可能。

對環境敏感的設計。在空間或者時間上分離方法的執行--例如,線程,遠程方法調用,消息隊列--能夠對設計的正確性和效率產生意義深遠的影響。這種分離帶來的結果是不可忽視的:並發引入了不確定性和環境選擇的開銷;分布引入了錯誤的和不斷增加的回程的調用開銷。這些是設計的問題,而不是修改bug那樣簡單。

無論是在何種情況下,結果都是將會阻礙所有權風格的程序設計(Property-Style Programming)--當一個接口主要由set和get方法組成的時候,每個方法都相應的直接指向私有區域。這樣的類的封裝很差(意思是毫無遮掩)。接口中的域訪問器(Field accessors)通常是不會提供信息的:他們在對象的使用中不能通訊、簡單化和抽象化,這通常會導致冗長並易出現錯誤的代碼。所有權風格的程序設計在短時間內不是一個大的活動。分布和並行通過引入了正確性和嚴重的性能開銷放大了這些格式上實踐的問題。

透明度和bug災難。抽象允許我們在必要的時候可以忽略細節,所以我們的設計思想可以平衡環境的因素而不是受制於它們。決定什麼樣的細節可以忽略便成為一個挑戰。問題的嚴重性在重要的細節別忽略的情況下上升了。

設計往往會盡量使環境因素盡可能的透明。透明能夠成為一個誘人的主意:也許它可以讓線程和遠程對象通訊完全透明,這樣用戶在進行對象通訊的時候什麼也不會覺察到。Proxy模式支持一定程度上的透明度。這加強了RMI和COBRA的基礎。本地的代理的對象和使用遠程的對象在使用中具有相同的接口,並且編組上的細節允許調用著使用熟悉的方法來調用模型。然而,這種分布透明並不完全:失誤和潛在的影響,不能被完全隱藏並且需要考慮。畢竟透明不是毛巾。

Command-Query Separation

保證一個方法是不命令(Command)就是查詢(Query)

問題。方法,當它們返回一個值來回應一個問題的時候,具有查詢的性質,當它們采取強制行動來的改變對象的狀態的時候,具有命令的屬性。所以一個方法可以是純的Command模式或者是純的Query模式,或者是這兩者的混合體。

例如,在java.util.Iterator中,hasNext可以被看作一種查詢,remove是一種命令,next和awkward合並了命令和查詢:

public interface Iterator
{
boolean hasNext();
Object next();
void remove();
}

如果不將一個Iterator對象的當前值向前到下一個的話,就不能夠查詢一個Iterator對象。這導致了一個初始化(initialization)、增加(continuation)、訪問(access)和前進(advance)分離而清晰定義的循環結構的錯位:

for(initialization; continuation condition; advance)
{
... access for use ...
}

將Command和Query功能合並入一個方法的的結果是降低了清晰性。這可能阻礙基於斷言的程序設計並且需要一個變量來保存查詢結果:

for(Iterator iterator = collection.iterator();
iterator.hasNext();)
{
Object current = iterator.next();
... use current...
... again use current...
}

解決方案。保證方法的行為嚴格的是命令或者是查詢,這樣可以返回值的方法是純的函數而沒有復效應(side effects),有負效應的方法不可能有返回值。"另一個表述這點的方法是問一個問題而不影響到答案。"

Combined Method

組合方法經常一起被使用在線程和分布環境中來保證正確性並改善效率。

問題。一些主要提供密集的方法的接口,起初,看來是最小化和附著性強的--都是吸引人的特點。然而,在使用的過程中,一些接口顯現得過於原始。它們過於簡單化,從而迫使類用戶用更多的工作來實現普通的的任並操縱方法之間的依賴性(暫時耦合)。這是非常麻煩並且容易出錯的,導致了代碼重復--代碼中應當避免的--並且為bug提供了很好的滋生條件。

一些需要同時執行成功的方法,在執行的時候在多線程、異常、和分布的地方遇到了麻煩。如果兩個動作需要同時執行,它們必須遵守協作或反轉(commit-or-rollback)語義學--它們必須都完全成功的執行或者一個動作的失敗會反轉另一個動作的執行--它們由兩個獨立的方法進行描述。

線程的引入使不確定程度大大增加。一系列方法調用一個易變的(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;
}

如果目標對象是遠程的,回程增加的開銷和對方法調用失敗並發的交織在一起成為環境的一部分。在上一個例子中,我們可以假設執行每一個方法體的時間和通訊的延遲相比是很短的。在這個例子中,開銷被重復了兩次,並可能在其他的實例中重復多次。

此外還有一個問題是對外部(extern)的synchronized同步塊的使用需求。Synchronized塊很明顯在分布的環境中使用但是也可以在本地的線程環境中應用的很好:在調用者和目標之間的代理對象的使用。簡而言之,對synchronized塊的使用因為代理對象而不是目標對象的同步而失敗。保守的說法是,這對系統的真確性可以有一個基本的影響。因為代理使用是在接口後透明的,調用者不能對行為做太多的保證。

解決方案。Combined Method必須在分布,線程環境中同時執行。聯合應當反映出普通的使用方法。這樣,一個Combined Method才可能比原有的方法要清晰,因為它反映了直接的應用。恢復策略和一些笨拙的方法被封裝到Combined Method中,並簡化了類用戶角度的接口。這改善的封裝降低了接口中不需要的累贅。Combined Method的全部效果是支持一種更像事務處理風格的設計。

在一個聯合的Command-Query中提供一個單獨的Query方法通常是合理的。然而,這需要按照需要而制定,而不是強制的執行。提供分離的Command方法是不太常見的,因為Combined Method可以完成這一工作:調用者簡單的忽略結果。如果返回一個結果招致一個開銷的話,才可能會體統一個單獨的Command方法。

回到前一個例子中,如果installHandler method返回上一個句柄設計變得更加簡單和獨立:

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會產生一個不同的正確性和適用性。

在另一個例子中,考慮獲得資源的情況。假設,在下面的接口中,獲得的方法在資源可用前一直起到阻礙作用:

interface Resource
{
boolean isAcquired();
void acquire();
void release();
...
}

類似於下面的代碼會在一個線程系統中推薦使用:

class ResourceExample
{
...
public void example()
{
boolean acquired = true;
synchronized(resource)
{
if(!resource.isAcquired())
resource.acquire();
else
acquired = false;
}
if(!acquired)
...
}
private Resource resource;
}

然而,即使放棄可讀性和易用性,這樣的設計不是一個Command-Query分離設計的應用。如果引入了代理,它就會失敗:

class ActualResource implements Resource {...}
class ResourceProxy implements Resource {...}
一個Combined Method解決了這個問題,它使並發和間接性更加透明。
interface Resource
{
...
boolean tryAcquire();
...
}

下面的代碼清晰、簡單並且正確:

class ResourceExample
{
...
public void example()
{
if(!resource.tryAcquire())
...
}
private Resource resource;
}

Combined Method帶來的一個結果是使一些測試和基於斷言的程序設計變得十分笨拙。然而,和原來的設計相比較,原有的方法在解決線程和分布問題上不是一個合適的途徑。在這一情況下,單元測試提供較好的分級和分離。Combined Method能夠使一個方法接口模糊並使類用戶的代碼更加冗長,笨拙。在一些條件下Execute Around Method提供了一個可以保證自動和靈活的另一個Combined Method。

結論

環境決定實踐的方法。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved