MVC 編程筆記1

轉載自https://www.ibm.com/developerworks/cn/java/j-lo-puremvc/index.html?ca=drs-html

引言

MVC 是一個很是常見的設計模式,被普遍運用於各類和用戶交互的軟件中,像 java Swing 類庫和 SWT、J2EE 中的 JSP、wxWidgets、QT 和 Gnome 等。能夠很不誇張的說,幾乎全部和 UI 有關的類庫,不論什麼語言,什麼平臺都或多或少有 MVC 的影子。可是通過這麼多年的發展,MVC 這個設計模式已經由最初 smalltalk 中的樣子發生了不少變化,除了數目讓人眼花繚亂外,不少 MVC 模型之間已經有了很大差別,不一樣的人提及 MVC 時,除了基本原理外,摻雜了不少平臺或者語言相關的東西,常常會引發不少誤解。PureMVC 是一個跨平臺跨語言的 MVC 通用框架,這個框架基於 MVC 模式設計,清晰簡單,瞭解這個框架會有助於從新認識 MVC 設計模式自己,或者可以提供一個基礎讓大多數人可以更清晰的交流基於 MVC 的設計方案。java

PureMVC 的簡介

PureMVC 是在基於模型、視圖和控制器 MVC 模式創建的一個輕量級的應用框架,這是一個開源框架,它最初是被用於 ActionScript 3 語言使用的 Adobe Flex、Flash 和 AIR 之上,如今已經移植到幾乎全部主要的軟件平臺之上見表 1。編程

表 1. PureMVC 支持平臺語言的一覽表
Language Targets
ActionScript 2 Flex 1.5, Flash 8, FlashLite
ActionScript 3 Flex 2, 3, Flash 9/CS3, AIR.
C# .NET 1.0/2.0 Silverlight, Windows Mobile and Pocket PC.
ColdFusion ColdFusion 8
haXe JavaScript, Flash 8, Flash 9 and the Neko VM.
Java Java Mobile, Standard and Enterprise Editions (ME, SE, EE), JavaFX, Servlets, Applets, and GWT
JavaScript Browser neutral
Objective C Apple iPhone and Mac
PHP PHP 5
Python Python 2.5 for wxPython, Google App Engine, Pyjamas [13]
Ruby  
C++ MSVC 8.0/9.0/10.0, MinGW 3.4.5, GNU G++, Embarcadero C++ 6.21

 

PureMVC 框架有兩個分支版本:標準和多核。標準版提供了一種簡單的編碼分離的方法,按照基本的 MVC 概念設計而成。多核版本支持模塊化編程,容許多個 PureMVC 應用運行在一塊兒。本文咱們主要介紹標準版的功能和使用。設計模式

PureMVC 基本結構

在 MVC 模式中,應用程序被分爲低耦合的三層:Model、View 和 Controller。PureMVC 在此基礎上有所擴充,經過模塊化設計,致力於進一步下降模塊間耦合性,建立了一個易於擴展限制很小的通用框架,圖一是 PureMVC 的設計圖。框架

圖 1. PureMVC 框架示意圖(摘自 PureMVC 官方網站)

圖 1. PureMVC 框架示意圖(摘自 PureMVC 官方網站)

(查看圖 1 的 清晰版本。)ide

在 PureMVC 實現的經典 MVC 元設計模式中,Model、View 和 Controller 分別由一個單例類來管理,合稱爲核心層或核心角色。 另外,在 PureMVC 中還提供了一個單例類 —— Façade,主要做用是做爲與核心層通訊的惟一接口,簡化開發複雜度。模塊化

從上圖中能夠看出,除了這幾個主要對象之外,框架還有以下類 Proxy、Mediator 和 Command,如下簡單介紹一下他們的做用。函數

  • Proxy 對象負責操做數據模型,與遠程服務通訊存取數據,這樣能夠保證 Model 層的可移植性。一般 Proxy 對象的引用保存在 Model 中。
  • View 保存對 Mediator 對象的引用。由 Mediator 對象來操做具體的視圖組件(View Component,它的做用還包括:添加事件監聽器,發送或接收 Notification,直接改變視圖組件的狀態。經過這樣,就能夠把視圖和控制它的邏輯分離開來。
  • Command 對象是無狀態的,只在須要時才被建立。Command 能夠獲取 Proxy 對象並與之交互,發送 Notification,執行其餘的 Command。常常用於複雜的或系統範圍的操做,如應用程序的「啓動」和「關閉」。應用程序的業務邏輯應該在這裏實現。

除了基本的對象結構之外,爲了解耦合,PureMVC 框架中引入了事件機制,這是個很是簡單觀察者設計模式,全部的事件都是一個 Notification,不一樣對象之間經過 Notification 來同步操做和交換信息。例如若是想更新界面中某個 Mediator,首先咱們定義 Notification 用於此目的,而後註冊 Mediator 監聽該 Notification,而後就能夠在程序中任何地方生成一個 Notification,經過事件機制,Mediator 就會接收到 Notification,而後更新須要的部分。整個過程 Mediator 只和 Notification 有關,沒有其餘依賴,有效的下降了對象之間的依賴程度。

介紹完 PureMVC 的基本結構和事件模型,咱們來看看一個典型的 PureMVC 應用如何構造。首先實際的應用程序都有一個 Façade 子類,這個 Façade 類對象負責初始化 Controller(控制器),創建 Command 與 Notification 名之間的映射,並執行一個 Command 註冊全部的 Model 和 View,一旦初始化的工做作完,對象之間的關係創建好之後,應用程序就能夠正常運行了。

PureMVC Java 版本的基本結構

首先咱們看看 PureMVC Java 標準的核心類圖,如圖 2 所示:

圖 2. Java 版本主類圖

圖 2. Java 版本主類圖

核心類的實現徹底體現了框架的設計意圖,由 IController、IView 和 IModel 三個接口來規範 MVC 三種角色的行爲,同時又提供了三個默認的實現類。

而後咱們在看看輔助性類的類圖:

圖 3. Java 版本輔助類圖

圖 3. Java 版本輔助類圖

(查看圖 3 的 清晰版本。)

從中能夠看出 IFaçade 是整個程序的入口和負責管理全部對象的類,其中能夠註冊 proxy、command 和 Mediator,經過 IFaçade 的子類,系統的各類資源獲得統一管理和調度。

而後能夠看到 INotification 是整個事件驅動模型的核心類,經過事件模型,整個應用程序的各個部分的耦合度變得很是低,同時用戶能夠自定義 INotification 的子類,從而實現最大的可定製性。

PureMVC 樣例程序 —— 用戶登錄

下面咱們用 PureMVC 來實現一個典型的應用程序通信錄,這個程序由於只是示範,因此只實現了基本的編輯和管理功能,下圖是程序運行時的截圖。

圖 4. 主界面

圖 4. 主界面

當用戶填寫正確的用戶名和密碼的時候,就顯示登錄成功的消息:

圖 5. 登錄成功

圖 5. 登錄成功

當用戶填寫錯誤的用戶名和密碼的時候,就顯示登錄不成功的消息:

圖 6. 登錄失敗

圖 6. 登錄失敗

首先,咱們來看看程序的入口代碼,即 main 函數所在的類 LoginFacade,這個類是 Façade 的子類,同時繼承了 IFacade 接口。

清單 1. LoginFacade.java 的源碼
 package login; 
 import org.puremvc.java.patterns.facade.Facade; 
 import org.puremvc.java.patterns.observer.Notification; 
 public class LoginFacade extends Facade { 
     public static final String STARTUP = "startup"; 
     public static final String SUBMIT_LOGIN = "submitLogin"; 
     public static final String LOGIN_SUCCESSFUL = "loginSuccessful"; 
     public static final String LOGIN_FAIL = "loginFail"; 
     public static LoginFacade getInstance(){ 
         if (instance == null) 
             instance = new LoginFacade(); 
         return (LoginFacade)instance; 
     } 
     protected void initializeController() { 
         super.initializeController(); 
         registerCommand(STARTUP, StartupCommand.class); 
         registerCommand(SUBMIT_LOGIN, LoginCommand.class); 
     } 
     @Override 
     protected void initializeModel() { 
         super.initializeModel(); 
         registerProxy(new UsersProxy()); 
     } 
     @Override 
     protected void initializeView() { 
         super.initializeView(); 
         registerMediator(new LoginScreenMediator()); 
     } 
     public static void main(String[] args) { 
         getInstance().notifyObservers(new Notification(STARTUP, null, null)); 
     } 
 }

LoginFacade 覆蓋父類的三個初始化方法:initializeController、initializeModel 和 initializeView,做爲單例,經過父類的構造函數分別調用了這三個函數,從而完成初始化工做。三個函數也很簡單,只是在 initializeController 中註冊全部的 Command 類,在 initializeModel 中註冊全部的 Proxy 類,在 initializeView 中註冊全部 Mediator 類。

Main 函數很簡單只是發送了一個 STARTUP 的消息,該消息會被 StartupCommand 類接受,從而執行相應的初始化動做。

而後,咱們來看 StartupCommand 類,這個類很簡單,在 StartupCommand 中只是設置了 lookandfeel,而後就調用系統中已經註冊了的 LoginScreenMediator 類的對象,而後讓其顯示在桌面上,限於 Swing 的 EDT 特性,全部這些工做都放在 SwingUtilities.invokeLater 中執行。

本例中 StartupCommand 沒有執行太複雜的工做,可是實際運行的系統,就能夠根據須要在 StartupCommand 中各類須要執行的初始化工做。

清單 2. StartupCommand.java 的源碼
 package login; 
 import javax.swing.SwingUtilities; 
 import javax.swing.UIManager; 
 import javax.swing.UIManager.LookAndFeelInfo; 
 import org.puremvc.java.interfaces.INotification; 
 import org.puremvc.java.patterns.command.SimpleCommand; 
 public class StartupCommand extends SimpleCommand implements Runnable{ 
     @Override 
     public void run() { 
         try { 
            for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) { 
                    UIManager.setLookAndFeel(info.getClassName()); 
                    break; 
                } 
            } 
            LoginScreenMediator mediator = (LoginScreenMediator) this.facade.
                retrieveMediator(LoginScreenMediator.NAME); 
            mediator.show(); 
         } catch (Exception e) {} 
     } 

     @Override 
     public void execute(INotification notification) { 
         SwingUtilities.invokeLater(this); 
     } 
 }

下面咱們接着看 LoginScreenMediator 類。

清單 3. LoginScreenMediator.java 的源碼
 package login; 
 import java.awt.event.*; 
 import javax.swing.*; 
 import org.puremvc.java.interfaces.INotification; 
 import org.puremvc.java.patterns.mediator.Mediator; 
 import org.puremvc.java.patterns.observer.Notification; 

 public class LoginScreenMediator extends Mediator implements ActionListener{ 
     public static final String NAME = "LoginScreenMediator"; 
     private LoginScreen dialog; 
     public LoginScreenMediator() { 
         super(NAME, null); 
     } 
     public void show(){ 
                  dialog = new LoginScreen(new javax.swing.JFrame(), true); 
                  dialog.addWindowListener(new java.awt.event.WindowAdapter() { 
                      public void windowClosing(java.awt.event.WindowEvent e) { 
                          System.exit(0); 
                      } 
                  }); 
                  dialog.btnLogin.addActionListener(this); 
                  dialog.setVisible(true); 
     } 
     public String[] listNotificationInterests() { 
         return new String[] { LoginFacade.LOGIN_SUCCESSFUL, 
 LoginFacade.LOGIN_FAIL }; 
     } 
     public void handleNotification(INotification note) { 
         String noteName = note.getName(); 
         if (LoginFacade.LOGIN_SUCCESSFUL.equals(noteName)) { 
             JOptionPane.showMessageDialog(null, "Login ok!"); 
             System.exit(0); 
         } else if (LoginFacade.LOGIN_FAIL.equals(noteName)) { 
             JOptionPane.showMessageDialog(null, "Login failed, try again"); 
         } 
     } 
     @Override 
     public void actionPerformed(ActionEvent e) { 
             String name = dialog.userName.getText(); 
             String pass = new String(dialog.password.getPassword()); 
             LoginVO userInfo = new LoginVO( name, pass ); 
             this.facade.notifyObservers(new 
 Notification(LoginFacade.SUBMIT_LOGIN, userInfo, null)); 
     } 
 }

通常來講,Mediator 這個類負責管理視圖,即 Swing 控件或者控件組,同時響應用戶的操做。本例中的 LoginScreenMediator 管理了 LoginScreen 這個 Swing 控件。LoginScreenMediator 的 show 函數的工做首先初始化 LoginScreen,LoginScreen 就是咱們顯示給用戶看的 JDialog 子類,其中有兩個 JLabel,一個 JText Field,一個 JPassword Field 和一個按鈕,這裏就不列出 LoginScreen 的代碼,能夠在附件中的源碼查看詳細代碼。

LoginScreenMediator 的 show 函數而後註冊了 Login 按鈕的 ActionEvent 事件,而後就顯示了 LoginScreen 控件。到此爲止,用戶界面的顯示工做就完成了。LoginScreenMediator 類中另一個須要注意的地方是 listNotificationInterests,在這個方法中該類說明了它所關注的 Notification:LOGIN_SUCCESSFUL 和 LOGIN_FAIL。之後一旦有出現了這兩個 Notification,LoginScreenMediator 類的 handleNotification 函數就會被調用。

下面咱們來看用戶事件處理部分,界面顯示以後,一旦用戶點擊填入用戶名和密碼,而後點擊 Login 按鈕,就會觸發 LoginScreenMediator 的 public void actionPerformed(ActionEvent e) 函數,在這個函數中只是獲取用戶名和密碼,而後把獲得的信息,經過 SUBMIT_LOGIN 這個 Notification 發送出去,由於本類的工做只是負責管理 View,具體如何驗證登錄信息則不是它關注的範圍,天然有其餘 Command 處理。

在前面的 LoginFacade 中註冊了一個 LoginCommand 類來處理 SUBMIT_LOGIN 這種類型的 Notification,下面是 LoginCommand 的源碼。

清單 4. LoginCommand.java 的源碼
 package login; 
 import org.puremvc.java.interfaces.INotification; 
 import org.puremvc.java.patterns.command.SimpleCommand; 
 public class LoginCommand extends SimpleCommand { 
     @Override 
     public void execute(INotification notification) { 
         UsersProxy usersProxy = (UsersProxy) 
 his.facade.retrieveProxy(UsersProxy.NAME); 
         LoginVO userInfo = (LoginVO) notification.getBody(); 
         if(usersProxy.checkLogin(userInfo)){ 
             sendNotification(LoginFacade.LOGIN_SUCCESSFUL, null, null); 
         } else { 
             sendNotification(LoginFacade.LOGIN_FAIL, null, null); 
         } 
     } 
 }

LoginCommand 接收到 Notification 以後,首先獲取其中的附加信息(用戶名和密碼),而後經過系統中已經註冊的 UsersProxy 來檢查用戶名和密碼是否,若是正確,就發送 LOGIN_SUCCESSFUL 的 Notification,不然就發送 LOGIN_FAIL 的 Notification。本例中 UsersProxy 的功能很是簡單,只是檢查一個內存中 map,看看接收的用戶名和密碼,是否和已經保存在 map 中預設值數據是否一致,實際上,這裏也能夠經過檢查 LDAP 或者數據庫之類的 UserStore 來執行,效果是相似的。

下面咱們看 LOGIN_SUCCESSFUL 和 LOGIN_FAIL 的 Notification 的處理,前面提到 LoginScreenMediator 類關注這兩個 Notification,因此它會在 handleNotification 函數中處理這兩個 Notification,限於示例,程序的功能就只是給用戶提示一個登錄成功或失敗的消息。

以上就是演示代碼的全部功能,從基本的代碼結構來看,類比較多,可是你們能夠看到每一個類都很簡單,職責也很清楚,同時每一個 Command、Proxy 和 Mediator 類之間的耦合度很小,Mediator 和 Proxy 甚至都不知道對方的存在,也依賴 Command 類,這樣一來,Proxy 和 Mediator 的複用程度變高了。

另外 Notification 機制,你們能夠看到程序有很強的相似工做流的性質,其實在實際使用過程當中,徹底能夠根據需求分析的結果,結合工做流來定義 Notification。 Notification 和 Swing 事件不同,它不關注於用戶的鍵盤等 UI 事件,它關注業務流程,因此也是一個很是有力幫助需求人員和設計人員之間的溝通。

總結

PureMVC 框架做爲一個成熟的通用框架,具備如下優勢:

  • 輕量級的庫,簡潔適度的設計
  • 極大的下降耦合度
  • 大量的文檔和成熟的應用範例
  • 平臺語言無關
  • 減小複雜度,讓熟悉 MVC 概念的設計人員之間溝通更容易
  • 代碼簡潔,簡單易學
  • 不依賴任何第三方的庫

從上面的介紹中,咱們能夠看到,在一個項目中根據實際狀況整合 PureMVC 框架,可讓可使得和 UI(不限於 Swing)相關部分設計更簡潔,並提供更多的可複用組件,加強需求和設計之間的溝通效果,有效提升項目的設計質量。

相關文章
相關標籤/搜索