為什麼越來越多的企業應用開發正在轉向元件框架和解決方案?元件架構是否有前途?我相信答案是肯定的,而且很快所有開發框架都將會是基於元件的——這是近在眼前的事情。下面讓我來向你揭示這一切的原因。
你怎麼來建設你的房子?一般你會從砌塊開始。我們可以將構建Web應用與構建你的鄉間小屋進行對比。你能夠快速構建一個非常好看的應用,而且它具有所有必需的功能。同樣,在你的房子裡面,每一間房間都是針對具體的需求來建立的,例如廚房、起居室、臥室或浴室。房子的佈局使你能夠通過走廊和樓梯很方便地在房間之間移動。
現在你能夠做得更好,而且能夠承擔建設一座更大更好的房子的投入——你也許希望擁有桑拿房、游泳池、影院以及一座滿是爬行動物的巨大的水族館☺。但要想改變房子的設計卻是件非常困難的事情。若要新增額外的設施,房子最終看起來也許就不那麼漂亮了。此外,由於你新增的這些設施必須放在不太方便的位置,它們也會影響房子使用的便利性,例如你必須穿過主臥室才能進入檯球室。
最後,你那漂亮又整潔的房子將擁有一堆不同的功能,但它會變得笨拙又不舒適。同樣的道理也適用於應用開發。
問題是,有沒有可能設計一款應用,能夠根據你的需求成長和改變?
元件是應用的積木式構件
元件是擴充套件應用功能的首要方法。建立元件的過程,與基於元件建立應用的過程<a name="_GoBack">有一些差異。元件不止應該提供有用的功能,還應該從一開始就設計成可複用的。
元件複用
元件應該採用鬆耦合方式設計以便於複用。為實現這一目標,不同的框架往往基於觀察者模式實現其事件模型。該模式允許多個接收者訂閱同一事件。
觀察者模式的實現最早出現在Smalltalk中。Smalltalk是一個基於MVC的使用者介面框架,現在它已經成為MVC框架的關鍵部分。我希望你能注意到,自Java 1.0版本起,觀察者模式就已經在Java中存在。下面讓我們深入瞭解它。
下面的UML圖展現了觀察者模式:
以下則是一段基本的Java實現:
public class ObservableX extends Observable { ... public void setAmount(double amount) { this.amount = amount; super.setChanged(); super.notifyObservers(); } } public class ObserverA implements Observer { public void public void update(Observable o) { // gets updated amount } } public class ObserverB implements Observer { public void public void update(Observable o) { // gets updated amount } } //instantiate concrete observableX ObservableX observableX = new ObservableX(); //somewhere in code observableX.addObserver(new ObserverA()); observableX.addObserver(new ObserverB()); //much later observableX.setAmount(amount);
它是這樣工作的:
首先我們建立一個ObservableX類的例項,將ObserverA和ObserverB例項新增到observableX物件中,然後在程式碼中的某個位置,我們使用setAmount方法設定“一定數量”的值。被觀察者(observable)類的功能將接收到的數量通知所有註冊的觀察者。
觀察者擔當著中介的角色,維持接收者的列表。當元件裡有事件發生時,該事件將被髮送到列表上的所有接收者。
由於這個中介角色的存在,元件並不知道其接收者。而接收者可以訂閱某個特定型別的不同元件中的事件。
當一個類使用事件將自身的變化通知觀察者時,該類就可以成為一個元件。而這可以通過觀察者模式來實現。
使用元件比建立元件容易
通過使用元件,你能夠快速建立各種窗體、皮膚、視窗以及介面中的其他合成元素。不過,為了能夠複用新的由元件建立的合成部分,應該將它們轉化為元件。
為了實現這一目標,你需要決定元件所要生成的外部事件,以及訊息傳遞機制。例如,你至少需要建立新的事件類並且定義介面或回撥方法以接收這些事件。
這個方式讓實現可複用的應用元件變得更復雜。當系統只是由少量合成元素組成時沒什麼問題——這時合成元素最多不超過10個。然而當系統包含數以百計的此類元素時,又當如何?
與之相反,不遵從這一方式將導致元素間的緊耦合,並且會把複用的機會降低到0。這反過來會導致程式碼複製,從而讓未來的程式碼維護變得更復雜,並將導致系統中的bug數量上升。
由於元件使用者往往不瞭解如何定義和傳遞他們自己的新事件,問題將變得更為嚴重。但他們可以輕鬆地使用元件框架提供的現成的事件。他們知道如何接收但不知道如何傳送事件。
為了解決這個問題,讓我們考慮如何簡化應用中使用的事件模型。
太多的事件監聽者
在Java Swing、GWT、JSF和Vaadin中,觀察者模式被用於實現多使用者能夠訂閱同一事件的模型,並將用於新增事件監聽者的列表作為其實現方式。相關事件一旦發生,將被髮送到列表上的全部接收者。
每個元件為一個或多個事件建立自己的事件監聽者集合。這將導致應用中類的數量不斷增多。反過來,這也會使系統的支援和開發變得更復雜。
藉助註解機制(annotation),Java找到了一條讓單個方法訂閱特定事件的道路。例如,考慮Java EE 6裡的CDI(Contexts and Dependency Injection,上下文和依賴注入)中對事件模型的實現。
public class PaymentHandler { public void creditPayment(@Observes @Credit PaymentEvent event) { ... } } public class PaymentBean { @Inject @Credit Event<<paymentevent> creditEvent; public String pay() { PaymentEvent creditPayload = new PaymentEvent(); // populate payload ... creditEvent.fire(creditPayload); } }
你可以看到,當PaymentBean物件的pay()方法被呼叫時,PaymentEvent被觸發。接下來PaymentHandler物件的creditPayment()方法接收了它。
另一個例子是Guava類庫中事件匯流排的實現:
// Class is typically registered by the container. class EventBusChangeRecorder { @Subscribe public void recordCustomerChange(ChangeEvent e) { recordChange(e.getChange()); } } // somewhere during initialization eventBus.register(new EventBusChangeRecorder()); // much later public void changeCustomer() { ChangeEvent event = getChangeEvent(); eventBus.post(event); }
EventBus註冊了EventBusChangeRecorder類的物件。接下來對changeCustomer()方法的呼叫會使EventBus接收ChangeEvent物件並呼叫EventBusChangeRecorde物件的recordCustomerChange ()方法。
現在你不需要為你的元件實現若干事件監聽者,在應用中使用事件也變得更簡單了。
當所有元件都同時在螢幕上展現時,使用事件匯流排是很方便的。如下圖所示,它們使用事件匯流排進行訊息交換。
這裡,所有元素——標題、左側的選單、內容、右側的皮膚——都是元件。
訂閱事件——別忘記取消訂閱
通過將事件監聽者替換為註解,我們在簡化事件模型使用的道路上前進了一大步。但即使如此,系統中的每個元件依舊需要連線到事件匯流排,然後,必須訂閱上面的事件並在正確的時間取消訂閱。
當相同的接收者多次訂閱同一個事件時,將會出現許多重複提醒,這種情況很容易出現。而相似的情況還會在多個系統元件訂閱同一事件時發生,這將會觸發一系列級聯事件。
為了能更好地控制事件模型,將工作與事件一起遷移到配置中,並讓應用容器負責事件管理是很有意義的。由於特定的事件僅在特定條件下有效,將這些事件的狀態管理遷移到配置中也是合理的。
下面是一段配置的例子:
<?xml version="1.0"?> <application initial="A"> <view id="A"> <on event="next" to="B"/> </view> <view id="B"> <on event="previous" to="A"/> <on event="next" to="C"/> </view> <view id="C"> <on event="previous" to="B"/> <on event="next" to="D"/> </view> <view id="D"> <on event="previous" to="C"/> <on event="finish" to="finish"/> </view> <final id="finish" /> </application>
檢視A中的“下一個(next)”事件觸發了向檢視B的轉變。在檢視B中,使用者可以通過“前一個(previous)”事件回到A,或是通過“下一個(next)”事件進入C。D檢視中的結束事件將轉入“最終(final)”狀態,將通知應用結束其中的工作流。
有限狀態機是專為這樣的需求設計的。狀態機是一種數學計算模型。它被設想為一種抽象的機器,可以處於有限數量的狀態中的一個,並且在同一時間裡只會處於一個狀態——這被稱為當前狀態。事件或條件將觸發向另一個狀態的轉變。使用這一方式,你能夠輕鬆地定義活動畫面,並讓某事件來觸發向另一個畫面的轉變。
使用有限狀態機來配置應用的好處
大部分情況下,應用配置是靜態定義的。使用依賴注入配置應用,我們在啟動時定義應用結構。但我們忘記了在探索應用的過程中它的狀態可能會改變。在應用的程式碼中,狀態改變往往屬於硬編碼,它讓未來的調整和維護變得複雜。
將狀態間的轉變遷移到配置中可以提高靈活性。而且這正是為什麼我們在建立諸如窗體、視窗或皮膚等複雜應用元素時,無需為了應用應該進入哪個狀態而操心。你可以稍後來處理它們,在配置中設定其行為。
所有元件都可以使用標準的事件傳送機制進行交流——即通過事件匯流排。同時,狀態機能夠控制元件事件到事件匯流排的訂閱。這一方式將應用的全部元件(窗體、視窗、皮膚)變為可複用元件,可以通過外部配置輕鬆地管理它們。
如果有興趣,你可以看一下Enterprise Sampler中一些配置的例子。
你也可以將狀態配置看作城市的公路圖,把事件看作遞送商品的汽車,而將城市裡的人看作目的地。
我確信採用這樣的方式,不僅能夠輕鬆地設計和構建一間規模雖小卻做好了成長準備的房子,還能夠建設擁有摩天大樓、公路和高速公路的城市。
關於作者
Aliaksei Papou是Lexaden.com的CTO、軟體架構師和聯合創始人,他擁有超過10年的企業級應用開發經驗,他對於技術創新有著強烈愛好。他與Lexaden.com的CEODenis Skarbichev(另一位聯合創始人)一同開發了可以建立大規模敏捷企業級應用的 Lexaden Web Flow語言。