博客首页|TW首页| 同事录|业界社区
2011-05-10

摘要

本文简单地对 pureMVC 和 robotlegs 进行了对比,并且较详细地说明了 robotlegs 各个角色的职责, 以及使用 robotlegs 的常见的注意事项。

正文

最近开始前端的开发,即flash开发,之前使用的是 pureMVC ,也写过一篇关于 pureMVC notification 的博客;而这个项目的开发是使用 robotlegs , 所以,这篇博客主要是基于过去这几周的工作和自己的理解对这个框架的一些简单 说明,希望对看到的朋友有益。

整体感受

做前端的开发不多,与后端相比一些UI上的处理、事件的处理等差异比较大,但是 编程的思想(如解耦、接口编程而非实现编程等)还是一致的,与后端比没有多大 可比性,所以我想简单地说明我对于 pureMVCrobotlegs 二者的一些 感受。

pureMVC 是基于 notification 的一个MVC框架,主要目的是为了各个部分 能够解耦,当然它也基本上能够做到。 robotlegs 则是基于 message 以及 message 携带的payload(数据等)来实现解耦。 robotlegs 是基于pureMVC 的 思想,但是在一些方面更加出色,例如消息的强类型,依赖注入方式,消息携带payload 等等。

当然,任何一个好的东西(语言、框架等)最终还取决于用的人,语言和框架本身并不能 保证用户的代码清晰、解耦等,当然它只是尽可能地做到这点。

关于robotlegs


还是一个MVC的框架,它的几个角色的主要功能有:

  1. M(odel): 提供数据的存储(与后端数据库表的映射),操作等

  2. V(iew): 呈现给用户,提供对用户操作的响应等

    • 通常一个V对应于一个Mediator
    • Mediator的职责是转发来自V的事件到整个框架,或者接收来自框架的事件并更新V(通常通过调用V的方法)
  3. C(ontroller): 业务逻辑,处理M和V之间的连动

    • 通常对应于一个Command
    • Command的职责有,实现业务逻辑,发送事件等

除此之外, robotlegs 还有一个角色是 service , 主要职责是提供一些获得数据的服务接口, 它本身并不存储(和model的区别)数据和提供数据的操作。

为了更好的说明起见,我想以一个员工考勤系统为例来说明各个角色的职责。

为了简化起见这个员工考勤系统只有如下功能:

  1. 提供一个界面供员工登录
  2. 提供一个界面供员工点击来作为考勤的时间

后端也可以很简单,例如有用户表User(id, name), 用户考勤表CheckInRecord(user_id, checkin_time), 那么前端使用robotlegs的结构可以采用:

使用robotlegs的注意事项

  1. 注意各个角色初始化的顺序(顺序关系到注入及事件处理的逻辑)
  2. 注意注入的使用(提前初始化)
  3. 其它的可参考 Best Practice

总结

还是一句话,框架或者语言只能在最大限度上保证用户代码的清晰、可读、解耦、可维护等(例如python语言),但是 最终的代码质量还是取决于用户本身(程序员),所以一些编程的哲学和模式才是更为关键的(大致可看下 Code Complete 、 Design Pattern 等)。

最近一直在看 Code Complete ,一句话让我感受非常深,大致意思是说 写代码的过程就是复杂度管理的过程(Complexity Management) , 这一点之前的认识不够,我推荐给看到的朋友,将它和 DRY 原则一样牢记在心吧。

下载原文

可从 此处 查看或者下载。

2011-04-05

因为工作需要,最近也开始学习和使用 pureMVC , 之前也写过一个 pureMVC Hello World教程, 算是开始入门这个框架和AS这种语言.经过两周多的学习和使用,生出了几个疑问, 和公司的做AS的同事 沟通时,发觉他也并不能清楚地说出所以然来,所以决定自行研究这些问题,并尝试给出解答.

问题大致有下面几个:

  1. 如果一个notification有多个订阅者,那么这多个订阅者之前的执行顺序是怎么样的?
  2. 一段代码在执行时,如果发出一个notification,是等待相应的notification订阅者处理结束再继续呢,还是 直接继续,相应的notification订阅者异步地执行?
  3. 一个notification的多个订阅者之间的执行是异步的,还是顺序执行的?
  4. 是否可能出现notification死循环的问题? (即notification A的订阅者X发出notification B,B的订阅者 又发出notification A,形成一个没有出口的循环)

研究和解决上述问题

为了准确研究出上述的问题和答案,我采用的方法包括两种: 实验和源代码阅读(其实只要阅读源代码即可).

实验

相关的代码可从 TestPureMVC 下载.

这个程序主要是如下的流程:

Startup -> StartUpCommand -> registerMediator(AMediator, BMediator, CMediator) -> sendNotification(Test) -> CMediator(Test) -> sendNotification(Second) ->CMediator(Second) ->sendNotification(Test)

从而形成了一个循环,另外,也可简单地在StartUpCommand类中更改来测试其它的情况.

从代码中能够说明(对应于本文最初的4个问题):

  1. notification的多个订阅者的执行顺序是按照其注册的顺序执行的,也就是全局的view中维护的mediator数组中的顺序
  2. 因为AS中没有类似于sleep的方法,所以无法确定2(具体结果参考下文,代码分析部分)
  3. 同2
  4. 会出现死循环,最终会出现栈溢出的错误

源代码分析

因为Command和Mediator都可以处理和发送notification,所以这里只以Mediator的处理为例来说明.

我们先看notification是如何通知的:

// org.puremvc.as3.core.Views.as
public function notifyObservers( notification:INotification ) : void
{
    if( observerMap[ notification.getName() ] != null ) {

        // Get a reference to the observers list for this notification name
        // 获得这个notification的所有订阅者数组
        var observers_ref:Array = observerMap[ notification.getName() ] as Array;

        // Copy observers from reference array to working array,
        // since the reference array may change during the notification loop
        //  notification的循环中可能增加新的订阅者,所以这里深度拷贝一份
        // 注意顺序并没有更改
        var observers:Array = new Array();
        var observer:IObserver;
        for (var i:Number = 0; i < observers_ref.length; i++) {
            observer = observers_ref[ i ] as IObserver;
            observers.push( observer );
        }

        // Notify Observers from the working array
        // 根据数组中的顺序依次来通知相应的订阅者
        // 注意这里是顺序执行的
        for (i = 0; i < observers.length; i++) {
            observer = observers[ i ] as IObserver;
            observer.notifyObserver( notification );
        }
    }
}

接着来看事件是如何注册的,从而形成订阅者的数组:

// org.puremvc.as3.core.Views.as
public function registerMediator( mediator:IMediator ) : void
{
    // do not allow re-registration (you must to removeMediator fist)
    if ( mediatorMap[ mediator.getMediatorName() ] != null ) return;

    // Register the Mediator for retrieval by name
    mediatorMap[ mediator.getMediatorName() ] = mediator;

    // Get Notification interests, if any.
    var interests:Array = mediator.listNotificationInterests();

    // Register Mediator as an observer for each of its notification interests
    if ( interests.length > 0 )
    {
        // Create Observer referencing this mediator's handlNotification method
        var observer:Observer = new Observer( mediator.handleNotification, mediator );

        // Register Mediator as Observer for its list of Notification interests
        for ( var i:Number=0;  i<interests.length; i++ ) {
            // 注意: 这里是根据Mediator有兴趣的notification来分别加入到对应的订阅者数组中
            // 而加入的次序正好是根据这个Mediator的注册顺序
            registerObserver( interests[i],  observer );
        }
    }

    // alert the mediator that it has been registered
    mediator.onRegister();

}

// registerObserver的具体实现
public function registerObserver ( notificationName:String, observer:IObserver ) : void
{
    var observers:Array = observerMap[ notificationName ];
    if( observers ) {
        // 顺序加入到对应的订阅者数组中
        observers.push( observer );
    } else {
        observerMap[ notificationName ] = [ observer ];
    }
}

我们再看通知给订阅者时的处理逻辑:

public function notifyObserver( notification:INotification ):void
{
    // 根据notification传过来的上下文来执行相应的处理逻辑
    this.getNotifyMethod().apply(this.getNotifyContext(),[notification]);
}

到此,从我们对源代码的分析上,我们就可以完整的回答本文初始的4个问题:

  1. 如果一个notification有多个订阅者,那么多个订阅者之间是按照注册的顺序来执行的
  2. 在AS中不存在异步的执行,所以,当前的代码的执行会等待所有的notification按照1中 的顺序执行完成后,才开始继续执行当前的代码(相当于调用一个函数)
  3. 一个notification的多个订阅者之间是顺序执行的,顺序是按照1中的说明
  4. 存在死循环的可能,因为如2中说明,sendNotification相当于根据注册的顺序来顺序执行 相应的处理逻辑,如果在处理逻辑中又包含触发sendNotification的事件,则整个执行 会成为一个死循环,从而导致栈溢出

总结

通过对本文开始4个问题的分析,弄清楚了 pureMVC 核心的notification机制的几个 核心问题,对于后续的工作和学习都是很有益处的.

2011-03-21

PureMVC 是大量使用于flash开发的一个框架,它很好地对处理对象进行了 最常见的MVC模式划分,清晰的逻辑和功能使得开发者能够很好地开发 新的功能,阅读旧的功能等,所以很多的flash的前端代码都是基于 PureMVC 开发的.

最近因为工作上的需要,我开始学习 PureMVC ,之前也简单地学习过,也按照 官方的教程完成过一个示例,并且也仔细阅读过 相关的教程 ,但是对于 PureMVC 的理解还是相对粗浅,这次在之前理解的基础上做出了一个最简单的 Hello World式的教程,希望加深自己对于这个框架的理解的同时,也能够帮助到 其他人.

PureMVC Hello World

这个示例的主要场景如下:

  1. 在UI上显示一个登录用的表单
  2. 表单上有name, password,和登录的按钮(代码中使用的还是TextField)
  3. 点击登录后,如果name,password匹配则显示登录成功,否则显示登录失败

基于上面的描述,再根据 PureMVC 的框架,我们大致可分为如下几个部分:

  1. 启动用的单例AppFacade

  2. Proxy:

    • LoginProxy: 用于验证用户提交的表单是否合法
  3. Command:

    • StartUpCommand: 用于启动整个应用的Command, 也兼做一些初始化的工作
    • initMVCCommand: 用于初始化并注册相关的MVC,如mediator, proxy等
    • LoginCommand: 用于处理Login时的逻辑,如调用LoginProxy来进行验证
  4. Mediator:

    • ViewMediator: 用于UI的显示

整个应用的流程为:

AppFacade(register command, send notification to start)-> StartUpCommand(execute) -> initMVCCommand(execute, register MVC)
-> ViewMediator(construtor, init the UI)

ViewMediator(Login Button Clicked) -> Login(Notification) -> LoginCommand(verfiy login using LoginProxy) -> Login_Succ/Login_Fail
-> ViewMediator(show the login result message)

那么下面我们分别看下重要的几个模块.

AppFacade

这是用于启动和关联整个 PureMVC 通信过程的辅助性类,单例的. 在这个类中主要注意,初始时的notification发送,注册初始时的Command等即可.

具体代码见: https://github.com/topman/blog_code/blob/master/HelloPureMVC/src/AppFacade.as

Proxy

Proxy主要功能是处理数据相关的信息,也即Domain logic(域逻辑),通常它只处理数据,保证数据的正确,完整性等. 而与此不同的是Command,它处理的是Business logic(业务逻辑),它更加关注应用所在业务的相关逻辑.

参考 Business Logic和Domain Logic的区别.

在示例中的Proxy, 它的主要功能是提供相关的数据, 因为登录验证是一个非常常见的功能,在实际的登录验证中, 通常是后端进行验证,返回一个成功或者失败即可,所以此例中将验证也放入了Proxy的逻辑中.

具体代码见: https://github.com/topman/blog_code/blob/master/HelloPureMVC/src/model/LoginProxy.as

Command

Command是处理应用的Business Logic(业务逻辑),与Proxy的区别参考上面Proxy的部分. 本例中的Command主要是包括三个,即:

  • StartUpCommand: 应用启动的命令,调用其它的初始化命令即initMVCCommand
  • initMVCCommand: 用于初始化整个MVC,注册MVC各个部分
  • LoginCommand: 用于处理登录逻辑的命令

具体的代码见: https://github.com/topman/blog_code/tree/master/HelloPureMVC/src/controller

注意在 PureMVC 中命令有MacroCommand和SimpleCommand两类,前者调用后者,后者则实现具体的业务逻辑.

当然所有的逻辑都可以使用SimpleCommand来完成,但是通常MacroCommand命令可以简化整个代码结构,使得 逻辑更加清晰易读.

Mediator

由于示例比较简单所以只有一个Mediator来处理相关的UI显示, Mediator通常的功能是显示, 接收UI的事件, 响应来自业务逻辑的UI变动等.

示例中主要是两个功能,一个是初始化时显示登录对话框,一个是对于Login_Succ和Login_Fail事件的响应. 相应的代码参考:https://github.com/topman/blog_code/blob/master/HelloPureMVC/src/view/ViewMediator.as

结语

我个人之前更多的是做后端开发,没有系统地接触过前端的开发,如Javascript, flash等. 第一次使用和了解 PureMVC , 发现其理念与后端的MVC框架是一致的,只是在更细层面进行了MVC的划分, 例如一个项目前端是flash,后端是 DjangoDjango 本身是MVC,而 PureMVC 又是在 Django 的V层上进行了更加细致的MVC划分.

Django 的MVC是简洁,清晰和优美的,了解 PureMVC 后,同样的特征也适用,我想在后续开发中可以比较熟练地加以应用, 也能够更加顺手地控制工作的进展,不至于项目的进展完全驱动于自己不熟悉的前端.