爲何愈來愈多的企業應用開發正在轉向組件框架和解決方案?組件架構是否有前途?我相信答案是確定的,並且很快全部開發框架都將會是基於組件的——這是近在眼前的事情。下面讓我來向你揭示這一切的緣由。html
你怎麼來建設你的房子?通常你會從砌塊開始。咱們能夠將構建Web應用與構建你的鄉間小屋進行對比。你可以快速構建一個很是好看的應用,並且它具備全部必需的功能。一樣,在你的房子裏面,每一間房間都是針對具體的需求來建立的,例如廚房、起居室、臥室或浴室。房子的佈局使你可以經過走廊和樓梯很方便地在房間之間移動。java
如今你可以作得更好,並且可以承擔建設一座更大更好的房子的投入——你也許但願擁有桑拿房、游泳池、影院以及一座盡是爬行動物的巨大的水族館☺。但要想改變房子的設計倒是件很是困難的事情。若要添加額外的設施,房子最終看起來也許就不那麼漂亮了。此外,因爲你添加的這些設施必須放在不太方便的位置,它們也會影響房子使用的便利性,例如你必須穿過主臥室才能進入檯球室。web
最後,你那漂亮又整潔的房子將擁有一堆不一樣的功能,但它會變得笨拙又不溫馨。一樣的道理也適用於應用開發。架構
問題是,有沒有可能設計一款應用,可以根據你的需求成長和改變?oracle
組件是應用的積木式構件app
組件是擴展應用功能的首要方法。建立組件的過程,與基於組件建立應用的過程<a name="_GoBack">有一些差別。組件不止應該提供有用的功能,還應該從一開始就設計成可複用的。框架
組件複用佈局
組件應該採用鬆耦合方式設計以便於複用。爲實現這一目標,不一樣的框架每每基於觀察者模式實現其事件模型。該模式容許多個接收者訂閱同一事件。post
觀察者模式的實現最先出如今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語言。