


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.
       */
   }
}
  controller只定義了用戶可以執行的并且能夠從下列接口抽象出來的actions。
public interface timecontroller {
   /**
    * action invoked when the user wants to start/stop the timer
    */
   void userstartstoptimer();
   /**
    * action invoked when the user wants to restart the timer
    */
   void userrestarttimer();
   /**
    * action invoked when the user wants to modify the timer period
    *
    * @param newperiod the new period
    */
   void usermodifyperiod(integer newperiod);
}
  你可以使用你自己喜歡的gui編輯器來畫這個view。出于我們自身的情況,我們只需要幾個公共的methods就可以提供足夠的功能來更新view的fields,如下面的這個例子所示:
/**
    * updates the gui seconds fields
    */
   public void setscnfld(integer sec){
      // scnfld is a swing text field
      swingutilities.invokelater(new runnable() {
         public void run() {
            scnfld.settext(sec.tostring());
         }
      });
   }
  在這里我們注意到我們正在使用pojos (plain-old java objects),同時我們不用遵守任何編碼習慣或者實現特定的接口(事件激發代碼除外)。剩下的就只有定義組件之間的綁定了。
事件分派annotations
  綁定機制的核心就是@modeldependent annotation的定義:
@retention(retentionpolicy.runtime) 
@target(elementtype.method) 
public @interface modeldependent {
   string modelkey() default "";
   string propertykey() default "";
   boolean runtimemodel() default false;
   boolean runtimeproperty() default false;
}
  這個annotation能被用在view的methods上,同時dispatcher也會使用這些提供的參數(即modelkey和propertykey)來確定這個view將會響應的model事件。這個view既使用modelkey參數來指定它感興趣的可利用的models又使用propertykey參數來匹配分配的java.beans.propertychangeevents的屬性名稱。
  view method setscnfld()因此被標注以下信息(這里,timemodel提供了用來將model注冊到dispatcher上的key):
/**
    * updates the gui seconds fields
    */
   @modeldependent(modelkey = "timemodel", propertykey = "seconds")
   public void setscnfld(final integer sec){
      // scnfld is a swing text field
      swingutilities.invokelater(new runnable() {
         public void run() {
            scnfld.settext(sec.tostring());
         }
      });
   }
  由于dispatcher既知道model激發的事件又知道事件本身-例如,它知道關聯的modelkey和propertykey-這是唯一需要用來綁定views和models的信息。model和view甚至不需要分享通信接口或者共用的數據庫。
  借助我們討論的綁定機制,我們可以輕易的改變潛在的view而不改變其他任何東西。下面的代碼是按照使用swt(standard widget toolkit)而不是swing實現的同一個method:
@modeldependent(modelkey = "timemodel", propertykey = "seconds")
   public void setscnfld(final integer sec){
      display.getdefault().asyncexec(new runnable() {
         public void run() {
            secondsfield.settext(sec.tostring());
         }
      });
   }
  一個完全沒有耦合的系統存在以下優點:view可以更加容易地適應model地改變,盡管model通常都是穩定地,相反view是經常被改變。加上系統可以通過使用gui編輯器或者其他源碼生成器來設計,避免了將生成地代碼與model-view通信代碼混合在一起。又由于model-view的綁定信息是和源碼關聯的元數據,于是也相對容易把它應用到ide生成的guis或者將已經存在的應用程序轉化成這個框架。加之擁有單獨的基礎代碼,view和model可以被當作是獨立組件來開發,這很可能簡化了應用程序的開發過程。組件測試也可以被簡化,因為每個組件可以被單獨地測試,并且出于調試的目的,我們可以用假的model和view來代替真實的組件。
  然而,這里也存在許多缺點。因為現在當使用接口和公共的classes來綁定model和view時,我們不能再提供編譯時期的安全性了,可能出現的打字錯誤將導致組件之間一個綁定的遺漏,從而導致出現運行時期的錯誤。
  通過使用@modeldependent的討論過的modelkey和propertykey元素,你可以定義model和view之間靜態的聯系。然而,現實世界的應用程序證明view必須能夠經常動態的適應變化的models和應用程序的狀態:考慮到用戶界面的不同部分能夠在應用程序的生命周期內被創造和刪除。因此我將介紹怎么使用這個框架與其他常用技術一起來處理此類情形。
動態mvc綁定
  對于那些依賴xml綁定(或者其他一些基于配置文件的聲明性綁定)的框架,存在一個問題那就是靜態綁定規則。在這些框架下,動態變化是不可能的,于是通常開發者決定每次將冗余的綁定信息與一些使用正確綁定的判定算法耦合在一起。
  為了巧妙的解決這個問題,stamps框架提供了兩種方式在運行時期改變綁定。 第一種方式是,views和models可以采用事件監聽器與gui窗口小部件聯合的方式在dispatcher上注冊和注銷。這樣允許特定的views只在需要他們的時候被通知到。例如,一個與應用程序有聯系的監視控制臺可以只在用戶請求的時候與被它監視的對象綁定在一起。
  第二種方式是利用@modeldependent annotation提供的兩個元素runtimemodel() 和 runtimeproperty()。他們指明了某個確定的model和它的分配事件會在運行時期被確定。如果這兩個設定中有一個是正確的,那么各自的key(modelkey 或propertykey)會在view上被method調用來得到需要使用的值。例如:一個負責顯示一組新channels (每個channel就是一個model)的view,它就依賴于用戶的輸入來確定需要綁定的channel。
這種情形的實例如下:
// this method is invoked to display all the messages of one news channel
   @modeldependent(modelkey = "dynamicchannel", propertykey = "allmessages" , runtimemodel = true)
   public void setallmessages(java.util.list messages) {
      // updates the user interface
   }
   public string getdynamicchannel() {
      // returns the channel requested by the user
   }
附加的annotations
  由于世界并不完美,一些附加的annotations被定義來幫助解決現實世界的案例。@namespace允許開發者為了更好的管理model domain將其再細分成不同的部分。由于單獨一個dispatcher可以處理多個models,model keys中將出現的沖突。因此,它能將成群的models和相關的views分到不同的但同屬一個namespace下的domains中去, 這樣一來,他們就不會干擾對方。
  @transform annotation提供了on-the-fly對象轉化, 從包含在model事件中的對象到被receiving views接受的對象的。因而,這個框架就可以適應已存的代碼而不需要做任何的改動。這個annotation接受一個注冊在有效轉化上的單一參數(被定義成一個特殊接口的實現)。
  @refreshable annotation能通過標注model的屬性來支持前面討論的動態連接和分離views。使用這個annotation,該框架可以處理靜態和動態的mvc布局,在不同的時間把不同的views綁定到model上。
  要理解@refreshable的使用,我們必須回到之前的那個監控控制臺的例子。這個控制臺(用mvc的術語來說就是一個view)可以動態地綁定和離開model,取決于用戶的需要。當控制器連接到model的時候@refreshable annotation可以被用來讓這個控制器隨時了解其model的狀態。當一個view連接到這個框架時,它必須在當前model的狀態下被更新。因此,dispatcher掃描model尋找@refreshable annotations并且生成與view它本身從model普通接受到的相同的事件。這些事件接著被之前討論過的綁定機制分派。
分布式mvc網絡
  dispatcher有一個很重的負擔那就是它負責處理事件的傳送周期中所有重型信息的傳遞:
·        model激發一個事件用來確定它已經經歷過的一些改變, dispatcher處理通知model.
·        dispatcher掃描所有注冊在它那里的views, 尋找@modeldependent annotations, 這些annotations明確了views希望通知的改變及當每個model事件發生時,需要在views上調用的method.
·        如果需要,轉化將會被用于事件數據上.
·        view method在被調用時會從被激發的事件里抽取參數,接著view會更新自己. 
  從另一個方面來講,當一個新view在dispatcher上注冊時:
·        view告訴dispatcher有關modelkey的信息,modelkey能確定它將被連接到哪一個model上(該model的事件將負責組裝view)
·        如果需要,dispatcher掃描model尋找@refreshable annotations并使用他們來生產將要及時更新view假的model事件
·        這些事件將通過使用上述的順序被分派, 接著view被更新.
  所有這些既不涉及view也不涉及model的工作,他們站在他們各自的信息通信渠道的兩端.無所謂這些信息是在一個本地jvm內部傳輸還是在多個遠程主機上的jvm之間傳輸.如果想將本地應用程序轉化成client/server應用程序所需的只是簡單地改變dispatcher里面的邏輯,而model和view都不會受影響.下圖就是一個示例:  
 
圖4. 一個基于分布式網路建立的mvc,點擊縮略圖查看全圖 
  如上圖所示,單一的dispatcher被一個與model處在同一個host上的transmitter(it.battlehorse.stamps.impl.broadcastdispatcher的一個instance)和一個(或多個) 與view處在同一個host上的receiver(it.battlehorse.stamps.impl.funneldispatcher)所取代. stamps 框架默認的實現使用了一個創建于jgroups上的消息傳送層, jgroups是一個可靠的多點傳送通信的工具包,象網絡傳輸機制(但是不同的實現和使用)一樣工作. 通過使用它可以獲得一個穩定可靠的, 多協議的, 失敗警覺的通信. 
  對我們應用程序(dispatcher)初步建立的一個改變, 使我們從一個單一用戶界面的獨立運行的應用程序轉移到一個多用戶分布式的應用程序.當model進入或離開這個網絡(想象一個通信失敗)的時候,框架可以通知無數的監聽接口, 于是遠程views可以采取適當的響應.例如,顯示一個警告信息給用戶. 這個框架也可以提供有用的methods來幫助將本地的controllers轉化成遠程的. 
總結和摘要
  仍有許多元素需要被探索,就像設計controllers的方式一樣,它在目前和dispatchers具有一致的普遍性.該框架假設普通的controller-model綁定,由于前者需要知道如何去驅動后者.未來的開發方向將是支持不同類型的views,例如使用一個web瀏覽器, 網絡警覺的applets,和java與javascript的通信.
  已經討論的stamps庫說明如何在一個mvc架構中降低views和models之間的耦合以及這個框架可以有效的利用java annotations將綁定信息從實際開發程序組件分離開.擁有隔離的綁定邏輯允許你在物理上將元件分離開并且能提供一個本地和一個client/server結構而不需要改變應用邏輯或者表示層. 這些目標提供對由一個象mvc一樣堅固的設計模式與由annotations提供的功能強大的元數據結合在一起所提供的可能性的洞察.
新聞熱點
疑難解答