當設計一個應用程序時, 清楚的分離該程序的不同邏輯組件, 總是被證實是有益的. 同時也存在許多不同的模式來幫助開發者實現這個目標。其中最有名同時也最常用的自然是Model-View-Controller (MVC)了, 它能夠將每個應用程序(或者應用程序的一部分)分成三個不同功能的組件,并且定義了把他們聯結在一起的規則。Swing本身就是基于這個模式的,而且每個使用Struts,這個流行的開發Web應用框架的人也都了解隱藏在MVC后面的理論.
這篇文章介紹了怎么樣通過使用annotation而增加一個新的組件來加強MVC,使其能夠更加方便地去掉models跟views之間的耦合。這篇文章介紹了一個叫Stamps的開源庫, 它是基于MVC組件之上的,但它去除了所有在開發MVC時所需的, 在models, views和controllers之間建立聯系的負擔。
基礎知識: MVC和annotations
正如MVC這個名字所指出的, Model-View-Controller模式建議將一個應用程序分成以下三個組件:
·Model: 包含了數據模型和所有用來確定應用程序狀態的信息。 它一般來說是有條理的并且獨立于其他組件的。
·View: 從不同于model的角度出發,它定義了存儲在模型中數據的展現方式。它通常被認為是你的應用程序的用戶界面(或者GUI),或者以Web應用為例,場景就是你通過瀏覽器看到的頁面。
·Controller: 它代表應用程序的邏輯部分。在這里,它定義了一個用戶如何和應用程序進行交互并且也定義了用戶行為是如何映射到model的改變。
這些組件緊密的聯系在一起: 用戶影響view, 反過來view通知controller來更新model.最終model又更新view來反映它的新狀態。圖1就展現了這種典型的MVC結構。
圖1. 一個典型的MVC結構
作為J2SE 5.0所提供的一個新的功能,annotations答應開發者往classes,methods,fields,和其他程序元素中增加元數據。就像反射機制一樣,之后很多應用程序為了某些原因能在運行時期獲取并使用那些元數據。因為J2SE 5.0只是定義了怎么樣編寫和讀取annotations,并沒有說明在哪里使用他們(象@Override這樣的用于提前定義的例外),開發者擁有無窮多的在許多不同場合使用他們的可能性:文檔編寫,與對象相關的映射,代碼生成,等等.. Annotations已經變的十分流行,以至于大多數框架和庫都更新自己來支持他們。至于更多的關于MVC和annotations的信息請參見資源。
超越MVC: dispatcher
就像前文提到的一樣,models和views之間的一些耦合是必要的因為后者必須反映前者的狀態。普通java程序使用直接或間接的耦合將組件綁定在一起。直接耦合發生在當view和model之間有一個直接相關的時候,model包含一列需要維持的views。間接耦合通常發生在一個基于事件分派的機制中。Model會在它狀態改變時激發事件,同時一些獨立的views會將他們自己注冊成事件偵聽器。
通常我們比較青睞間接耦合因為它使model完全不知道view的存在,相反view必須和model保持一定的聯系從而將自己注冊到model上。在這篇文章里我將介紹的框架就是使用間接耦合,但是為了更好的降低組件之間的耦合,view必須不知道model的存在;也就是說,model和view沒有被綁定在一起。
為了實現這個目標,我已經定義了一個新的組件,就是dispatcher,它能作為一個存在于views和models之間的分離層。它能處理models和views雙方之間的注冊并且分派由model激發的事件到注冊的views上。它使用java.beans.PRopertyChangeEvent對象來表現由model傳送到view的事件;然而,這個框架的設計是足夠開放的,它可以支持不同事件類型的實現。
治理注冊的views列表的負擔于是就從model上移開了,同時,因為view只和這個獨立于應用程序的dispatcher有關,view不知道model的存在。假如你熟悉Struts內部,你也許能夠看出Struts的controller就是在履行這樣一個任務,它將Actions和他們關聯的jsp(JavaServer Pages)表現頁面聯系在一起。
現在,我們所設計的MVC框架就像圖2所描述的一樣。Dispatcher在其中擔當了一個于controller相當的角色。
圖2.擁有額外dispatcher組件的改進的MVC框架
由于dispatcher必須是獨立于應用程序的,所以必須定義一些通用的聯結models和views的規范。我們將使用annotations來實現這種聯結,它將會被用來標注views并且確定哪個view是受哪個model的影響的,及這種影響是怎么樣的。通過這種方式,annotations就像是貼在明信片上的郵票一樣,驅動dispatcher來執行傳遞model事件的任務(這就是這一框架名字的由來)。
應用實例
我們將使用一個簡單的計秒器應用程序做該框架的一個應用實例:它答應用戶設置時間周期來記數和啟動/停止這個定時器。 一旦過去規定的時間,用戶將會被詢問是否取消或者重啟這個定時器。這個應用程序的完全源代碼可以從項目主頁上找到。
圖3.一個簡單的應用程序
這個modle是非常簡單的,它只存儲兩個屬性:周期和已經過去的秒數。注重當它其中一個屬性發生變化時它是如何使用java.beans.PropertyChangeSuppor來激發事件。
public class TimeModel {
public static final int DEFAULT_PERIOD = 60;
private Timer timer;
private boolean running;
private int period;
private int seconds;
private PropertyChangeSupport propSupport;
/**
* Getters and setters for model properties.
*/
/**
* Returns the number of counted seconds.
*
* @return the number of counted seconds.
*/
public int getSeconds() {
return seconds;
}
/**
* Sets the number of counted seconds. propSupport is an instance of PropertyChangeSupport
* used to dispatch model state change events.
*
* @param seconds the number of counted seconds.
*/
public void setSeconds(int seconds) {
propSupport.firePropertyChange("seconds",this.seconds,seconds);
this.seconds = seconds;
}
/**
* Sets the period that the timer will count. propSupport is an instance of PropertyChangeSupport
* used to dispatch model state change events.
*
* @param period the period that the timer will count.
*/
public void setPeriod(Integer period){
propSupport.firePropertyChange("period",this.period,period);
this.period = period;
}
/**
* Returns the period that the timer will count.
*
* @return the period that the timer will count.
*/
public int getPeriod() {
return period;
}
/**
* Decides if the timer must restart, depending on the user answer. This method
* is invoked by the controller once the view has been notified that the timer has
* counted all the seconds defined in the period.
*
* @param answer the user answer.
*/
public void questionAnswer(boolean answer){
if (answer) {
timer = new Timer();
timer.schedule(new SecondsTask(this),1000,1000);
running = true;
}
}
/**
* Starts/stop the timer. This method is invoked by the controller on user input.
*/
public void setTimer(){
if (running) {
timer.cancel();
timer.purge();
}
else {
setSeconds(0);
timer = new Timer();
timer.schedule(new SecondsTask(this),1000,1000);
}
running = !running;
}
/**
* The task that counts the seconds.
*/
private class SecondsTask extends TimerTask {
/**
* We're not interested in the implementation so I omit it.
*/
}
}
新聞熱點
疑難解答