实战 PureMVC
最近看PureMVC,在IBM开发者社区发现此文,对PureMVC的讲解清晰简洁,看了可快速入门。另外,《腾讯桌球》游戏的开发者吴秦,也曾进一步剖析PureMVC,可结合看加深理解。
引言
MVC 是一个非常常见的设计模式,被广泛运用于各种和用户交互的软件中,像 java Swing 类库和 SWT、J2EE 中的 JSP、wxWidgets、QT 和 Gnome 等。可以很不夸张的说,几乎所有和 UI 有关的类库,不论什么语言,什么平台都或多或少有 MVC 的影子。但是经过这么多年的发展,MVC 这个设计模式已经由最初 smalltalk 中的样子发生了很多变化,除了数目让人眼花缭乱外,很多 MVC 模型之间已经有了很大差异,不同的人说起 MVC 时,除了基本原理外,掺杂了很多平台或者语言相关的东西,经常会引起很多误解。PureMVC 是一个跨平台跨语言的 MVC 通用框架,这个框架基于 MVC 模式设计,清晰简单,了解这个框架会有助于重新认识 MVC 设计模式本身,或者能够提供一个基础让大多数人能够更清晰的交流基于 MVC 的设计方案。
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 实现的经典 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 版本主类图
核心类的实现完全体现了框架的设计意图,由 IController、IView 和 IModel 三个接口来规范 MVC 三种角色的行为,同时又提供了三个默认的实现类。
然后我们在看看辅助性类的类图:
图 3. Java 版本辅助类图
(查看图 3 的 清晰版本。)
从中可以看出 IFaçade 是整个程序的入口和负责管理所有对象的类,其中可以注册 proxy、command 和 Mediator,通过 IFaçade 的子类,系统的各种资源得到统一管理和调度。
然后可以看到 INotification 是整个事件驱动模型的核心类,通过事件模型,整个应用程序的各个部分的耦合度变得非常低,同时用户可以自定义 INotification 的子类,从而实现最大的可定制性。
PureMVC 样例程序 —— 用户登陆
下面我们用 PureMVC 来实现一个典型的应用程序通讯录,这个程序因为只是示范,所以只实现了基本的编辑和管理功能,下图是程序运行时的截图。
图 4. 主界面
当用户填写正确的用户名和密码的时候,就显示登陆成功的消息:
图 5. 登陆成功
当用户填写错误的用户名和密码的时候,就显示登陆不成功的消息:
图 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)相关部分设计更简洁,并提供更多的可复用组件,增强需求和设计之间的沟通效果,有效提高项目的设计质量。
下载
描述 | 名字 | 大小 |
---|---|---|
本文样例代码 | Login.zip | 6 KB |
参考资料
学习
- 参考:PureMVC 官方网站。
- 阅读:PureMVC 标准版和多核版的简单思考。
- 技术书店:浏览关于这些和其他技术主题的图书。
- developerWorks Java 技术专区:数百篇关于 Java 编程各个方面的文章。
实战 PureMVC的更多相关文章
- SSH实战 · 唯唯乐购项目(上)
前台需求分析 一:用户模块 注册 前台JS校验 使用AJAX完成对用户名(邮箱)的异步校验 后台Struts2校验 验证码 发送激活邮件 将用户信息存入到数据库 激活 点击激活邮件中的链接完成激活 根 ...
- GitHub实战系列汇总篇
基础: 1.GitHub实战系列~1.环境部署+创建第一个文件 2015-12-9 http://www.cnblogs.com/dunitian/p/5034624.html 2.GitHub实战系 ...
- MySQL 系列(四)主从复制、备份恢复方案生产环境实战
第一篇:MySQL 系列(一) 生产标准线上环境安装配置案例及棘手问题解决 第二篇:MySQL 系列(二) 你不知道的数据库操作 第三篇:MySQL 系列(三)你不知道的 视图.触发器.存储过程.函数 ...
- Asp.Net Core 项目实战之权限管理系统(4) 依赖注入、仓储、服务的多项目分层实现
0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有 1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端 2 Asp.Net Core 项目实战之 ...
- 给缺少Python项目实战经验的人
我们在学习过程中最容易犯的一个错误就是:看的多动手的少,特别是对于一些项目的开发学习就更少了! 没有一个完整的项目开发过程,是不会对整个开发流程以及理论知识有牢固的认知的,对于怎样将所学的理论知识应用 ...
- asp.net core 实战之 redis 负载均衡和"高可用"实现
1.概述 分布式系统缓存已经变得不可或缺,本文主要阐述如何实现redis主从复制集群的负载均衡,以及 redis的"高可用"实现, 呵呵双引号的"高可用"并不是 ...
- Linux实战教学笔记08:Linux 文件的属性(上半部分)
第八节 Linux 文件的属性(上半部分) 标签(空格分隔):Linux实战教学笔记 第1章 Linux中的文件 1.1 文件属性概述(ls -lhi) linux里一切皆文件 Linux系统中的文件 ...
- Linux实战教学笔记07:Linux系统目录结构介绍
第七节 Linux系统目录结构介绍 标签(空格分隔):Linux实战教学笔记 第1章 前言 windows目录结构 C:\windows D:\Program Files E:\你懂的\精品 F:\你 ...
- Linux实战教学笔记06:Linux系统基础优化
第六节 Linux系统基础优化 标签(空格分隔):Linux实战教学笔记-陈思齐 第1章 基础环境 第2章 使用网易163镜像做yum源 默认国外的yum源速度很慢,所以换成国内的. 第一步:先备份 ...
随机推荐
- Java面试题—初级(4)
31.String s = new String("xyz");创建了几个StringObject?是否可以继承String类? 两个或一个都有可能,"xyz" ...
- Python 破解带密码保护的Zip文件
今天发生了个有趣的事情,有个朋友发了一个带密码保护的Zip文件给我,却不给我密码,我就琢磨这怎么可以'猜'到密码呢? 经过一系列的尝试,最终使用python把这个密码给'猜'出来了.要想写出破解密码的 ...
- Bank方案SQL
用于演示的Bank方案对应的SQL: /* 1.branch 开展银行交易业务的场所 */ DROP TABLE IF EXISTS branch; CREATE TABLE branch -- 开展 ...
- ubuntu安装mysql并修改编码为utf-8
参考地址:ubuntu中文 sudo apt-get install mysql-server mysql-client -y # 中途会要求输入一下root用户的密码 编辑/etc/mysql/co ...
- spring data学习
在Spring Data模块中定义依赖: <dependencies> <dependency> <groupId>org.springframework.data ...
- ABP领域层知识回顾之---工作单元
1. 前言 在上一篇博文中(http://www.cnblogs.com/xiyin/p/6842958.html) 我们讲到了ABP领域层的仓储.这边博文我们来讲 工作单元.个人觉得比较重要.文 ...
- [SDOI2016]排列计数
Description 求有多少种长度为 n 的序列 A,满足以下条件: 1 ~ n 这 n 个数在序列中各出现了一次 若第 i 个数 A[i] 的值为 i,则称 i 是稳定的.序列恰好有 m 个数是 ...
- [HAOI2008]下落的圆盘
Description 有n个圆盘从天而降,后面落下的可以盖住前面的.求最后形成的封闭区域的周长.看下面这副图, 所有的红 色线条的总长度即为所求. Input 第一行为1个整数n,N<=100 ...
- [SCOI2008]城堡
题目描述 在一个国家里,有n个城市(编号为0 到n-1).这些城市之间有n条双向道 路相连(编号为0 到n-1),其中编号为i的道路连接了城市i和城市ri(一条道 路可以连接一个城市和它自身),长度为 ...
- 计蒜客NOIP模拟赛(2) D2T3 银河战舰
[问题描述] 瑞奥和玛德利德是非常好的朋友.瑞奥平时的爱好是吹牛,玛德利德的爱好是戳穿瑞奥吹的牛. 这天瑞奥和玛德利德来到了宇宙空间站,瑞奥向玛德利德炫耀这个空间站里所有的银河战舰都是自己 ...