如何從組件開始構建一座城市?
英文原文:How Would You Build Up a City from Components?
為什么越來越多的企業應用開發正在轉向組件框架和解決方案?組件架構是否有前途?我相信答案是肯定的,而且很快所有開發框架都將會是基于組件的——這是近在眼前的事情。下面讓我來向你揭示這一切的原因。
你怎么來建設你的房子?一般你會從砌塊開始。我們可以將構建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語言。
留言列表