Flash 务实主义(四)——Flash 中的 MVC

  • flashyiyi

2011 年 4 月 13 日

话题:Java.NETRuby语言 & 开发

FLASH 与传统环境的不同点

MVC 最早在 1979 年的时候第一次被人提出。不过,当时还不存在网络应用的概念。之后当万维网诞生之后,又过了很长时间……

它并不是自诞生就开始流行的,而改变的原因很简单——因为两个极其流行的开发框架包含了这种模式,它们就是:Struts 和 Ruby on Rails。之后,模仿者蜂拥而至。所以,在人们眼里看来,实际上是先有的 Struts,然后才有的 MVC,也无怪乎 MVC 的概念会始终沾染着 Web 概念,乃至和一些框架附加内容牵涉不清。

因为 Struts 很好用,别的不说,至少让 HTML 显得干净了很多。所以很多人都在用 Struts,这未必是因为需要 MVC 模式,而是因为他们需要 Struts。因此,当环境变化后,我们不使用 Struts 而是在使用一些其他的框架的时候,是否还应该像以前那样使用 MVC 框架就成为了一个问题。因为环境不同,即使在其他语言中使用 MVC 框架很普遍,也不代表在新环境里同样应该是如此。

AS3 与传统语言的不同点:

  • AS3 是单一语言环境,多层代码混在一起问题没那么严重。
  • AS3 正常情况都是一次性编译全部代码,即使用了 MVC 框架还是需要一起编译。单独编译一个模块减少编译时间有别的办法,不需要依赖 MVC。
  • AS3 本身的事件和动态特性和一些框架的功能重复。
  • AS3 目前的框架还很不成熟,没有提供比较醒目的功能。

结果是,至少,目前 AS3 的 MVC 框架比起传统语言并没有那么突出的作用,就算用了,也不会像 Struts 那样有质的变化。而且,至少在我看来,AS3 的框架使用成本却不见得比 Struts 低。两者相减,结果就很麻烦了。

而且,AS3 在不使用框架的时候有它自己的优势,使用框架会毁掉这些优点:

  • 有一个相对还可以的调试器,使用了框架会调试上产生麻烦,主要体现在单步调试步骤变多的问题上。
  • 阻碍使用 IDE 的功能。以 Flex Builder 为例,你可以通过 Ctrl+ 单击(F4)跳转到指定方法的具体实现,通过搜索引用面板从方法的实现跳转到调用方法的位置。使用框架后,这些功能都会失效。
  • Flex framework 相关功能会难以使用,诸如绑定。而且,Flex Builder 支持拖拽式的将数据接口绑定到视图的功能,可以部分实现零代码编程,框架也会阻碍这个过程。

此外,企业应用和网站还好说,游戏还有另一种情况。游戏的结构并不同于原来的专门用于呈现数据的结构,可能也就是其中的用户界面 (User Interface) 部分和以前的结构比较类似,其他的诸如地图,诸如人物,无论怎么想也无法套用 MVC 框架,首先从效率上就说不过去。举个例子,一个项目有 3 个客户端人员在开发,一个在做地图,一个在做战斗,一个在做 UI。前两者都和 MVC 没什么关系,结果只有一个人在用 MVC 框架开发界面……而且,开发前两者的时候,开发以及协作难度其实是比开发界面要高的,既然他们都搞定了,为什么开发界面的人还必须靠框架辅助才能解决这个问题?

这使得 FLASH 比起一般的情况,会更加不适合使用 MVC 框架。

不使用现有框架并非无法实现 MVC

既然我在说框架不好用。那么不用框架,我们又该怎么做呢?

实际上,如果你只是想实现单纯的模型—视图—控制器 (Model View Controller) 分工职守,它只是一个架构模式而已。将模型和视图的代码分开,并提出控制器的代码,然后互相调用各自方法就算完事了。Model 的全部引用放在固定的位置,View 的引用使用静态属性储存或者用管理类管理,Command 可以作为函数或者类直接初始化并执行,亦可以通过反射。这并不需要专门的工具类来辅助,附加成本也比较小,自然就可以适用于任何规模的项目。

当然,你可以实现一个简单的通信框架,提供必要的功能,如果你需要的话。这和使用一些专门的 MVC 框架需要的成本是完全不同的。

然而,我的意见则是——MVC 是非常好的架构模式,不管什么样的项目都建议尝试使用,但是用框架的话,请务必谨慎。

关于最简的 MVC,最近看到一个让人很囧的例子。不过这个例子对大家理解 MVC 是有帮助的。

这玩意的确……基本算是 MVC,只差一点而已。MVC 是架构模式,至少结构上要分开,即使不分文件至少要让能看得出来谁是谁(原文可没有红字),所以只需要把这些代码分成三个文件,那就可以称得上是最简的 MVC 了。

这是我在他的下面补充的代码。

结果是,View 只关心与自己相关的 Model 和 Command,Command 只关心与自己相关的 View 和 Model,Model 谁都不关心,这和一般情况需要的解耦目标是一致的。虽然这样并不算完全解耦,但是至少在思路和逻辑分离上是做到了,仅仅是协作方面存在问题,比如无法实现自由的并行开发,而这个加入简单的反射也可以解决。

所以,单纯的 MVC 并不困难,没什么要不要放弃一说。还有就是上面只是极端例子,但就算是这种东西,比起完全不实现 MVC,也至少实现了 50% 以上的内容。

是否使用框架应当理性对待

程序员都是理科生,应当用理科生的思考方式(当然我并没有让你们都去模仿 Sheldon)。在使用 MVC 框架的过程中,不管是觉得好,还是差,都要考虑清楚问题的源头在哪。

觉得 MVC 框架不好用,降低效率,是否曾经有过平行对比的例子,你能否确认不用它效率就确实能提高?效率低有没有可能是框架之外的原因?

觉得 MVC 框架好用,提高效率,是否有平行对比的例子?你怎么就知道是使用了框架的功劳,而不是规范了代码结构,制定了新的协作流程,甚至是开发人员水平提高的功劳?怎么知道 MVC 框架并没有起了反效果?

使用了框架,看到了结果,然后根据结果的好话直接判定框架的好坏,这太武断了,作为一个理科生,我们绝对不能这样做。至于那类连比较都没有,而是以“我用了框架,项目依然完成了,没有因为用了框架而失败”这种理由来支持使用某个框架的人,我无言以对。

推荐 MVC 框架

我并没有完全反对使用 MVC 框架。这要看你的项目类型,规模,人数。满足条件的时候当然可以使用。尤其是在企业应用里,如果你有幸出现六个客户端的话,没 MVC 框架可能还真是会出问题。

pureMVC 和Cairngorm是两个较早出现的框架,目前我不建议再使用它们。pureMVC 的问题在于过于强调分离而缺乏实际功能,提供的便利很难抵消它本身的消耗,性价比较低。Cairngorm的问题则在于过于强调模型更新视图的流程,限制太多,灵活程度不够。

后出的几个框架就好多了,Mate 使用了一个全局事件定义,配合 FLEX 写法非常简略。Swiz 则是用控制反转 + 依赖注入,也就是 Spring 的做法,而且元标签注入的方式很有趣,感兴趣的可自行查阅资料。

我这里要说的是 Robotlegs。这是一个和 Swiz 非常相似的框架,但也有一些自己的特点。首先它是基于 pureMVC 的,你依然可以像 pureMVC 这样来使用它,对于相信 pureMVC 的团队它是很容易接受的代替品。他让 pureMVC 也同样拥有了控制反转和依赖注入,包装了大部分功能,配置代码大大减少,而且不管用不用 FLEX framework 都可以很自然地使用它。

Robotlegs 的教程可以看这里:

http://wenku.baidu.com/view/42a08b235901020207409c60.html

但我得提醒大家,虽然我觉得 Robotlegs 很便利以及有趣,但是并没有在项目里使用它,因为我的项目规模不大,而且是游戏。实际上,我甚至自己实现了一个依赖注入框架,可以很简单的加入到现在的项目中,成本几乎为零,却依然没有去用。使用一个东西要看是否需要去用,而不是可以用就用,更不是“因为用了没有遇到问题所以就用”。用一个东西必须有收益才可以,尤其是在明明看到有损失的时候。仅仅是用“看起来更正规”这类自我满足的理由来决定自己的行为,太愚蠢了。

当然,如果你需要它,那就应该毫不犹豫的使用。不要受到抱怨框架的人影响,他们大部分都有自己的问题,提出的理由也未必是正确的,你并不一定会赴他们后尘——前提是你真的需要它,而且,要将使用框架需要的条件全部补齐。

就算是使用 MVC 框架也不需要完全解耦

解耦是一个扩展性要求,但扩展性要求并不是越多越好的。

这是一个普遍的误区。诸如使用 pureMVC 的人,很多都纠结于完全的解耦,以至于用了 Command,在 Command 中改变 View 的时候还是必须要发一遍 Notification。

Command 这种类,一般都是在相关的 View,Model 完成后才开始编写的。比如普通的 StartupCommand,OpenWindowCommand,没有对象又如何编写?它在编写顺序上应该是,就算不是也是可以放在 View 和 Model 之后的。那么在协作关系上,他就可以直接访问所有相关类,不需要为了这种原因而解耦。

虽然 pureMVC 将消息全局化了,但是消息实际上是分局部和全局的。比如一个 Proxy 发生变化要求所有监听某个消息的 View 更新,那么当然应该发一个叫做 I_AM_CHANGED 的 Notification,并由不同的 View 来监听这个消息并更新,这个就应该是全局消息。但有些消息就是局部的,是一对一的,比如一个叫做 SEND_DATA_TO_WINDOW1 的消息,按它的字面意思就应该是刷新 WINDOW1,那么由它来触发的 Command,就应该直接耦合 WINDOW1 这个 View 来设置值,而不是再发个类似 REFRESH_WINDOW1 的消息,因为 SEND_DATA_TO_WINDOW1 的名字已经确定是针对这个 View 了,如果最终它却没有操作这个 View,那才是有问题的吧?

解耦归解藕,但是对于已经有了意义上的联系的模块,结果却不耦合,在任何时候都是没有意义的。即使需求变化,逻辑变化了,使得 SEND_DATA_TO_WINDOW1 最终不是改变 WINDOW1 的数据,而是 WINDOW2 的数据,那么这个 Command 连带相关 Notification 的名字就必须修改,也就是说,意义上的紧密联系,在实际操作上和耦合了是一回事。既然已经是这样了,再做成不耦合,给自己制造麻烦的又有什么意义呢?

除了上面的情况,我们也要考虑,真的有必要将项目拆得那么细致么,有没有必要为了 1% 以下的可能性来解耦两个相关性很强的部分?比如一个叫做 ShopPanel 的 View 和一个叫做 ShopModel 的 Model,到底在什么情况下,ShopPanel 会不去调用 ShopModel,而是别的东西?而且 Panel 上可能还有各种文字,使得自己意义上只能调用商店的数据。真是要调别的东西,应该重新制作一个新的 View 吧?而且别忘了 pureMVC 是可以多个 Mediator 套用一个 View 的。这种情况下,我们直接在 Mediator 中耦合 ShopModel,有什么不可以的?当然,反过来,ShopModel 被多个 View 调用的情况很普遍,所以我们不能让它来耦合 ShopPanel。这些规则实际上是很明确的,是完全可以预知的。就算预知错误,也是很容易修正的。

解耦是手段,而不是目的。盲目的最求扩展性,只会让自己的程序变成一盘散沙。正是适量的耦合,程序才能拥有一个确定的形态,才不会让人感到茫然。

普遍误解

其实现在使用框架的人群里,真的能够发挥框架长处的确实比较少,尤其是在水平层次较低的 Action Script 开发人员之中。一方面,这污染了框架的名声,同时也是不建议使用框架的理由之一,因为人员水平限制也是实际项目中不可回避的现实问题。

如果你决定使用 MVC 框架,就必须提高自己的认识。我拣几个最常见的问题来说吧。

  • 并不是用了消息通信就算用了 MVC
  • 消息通信只是一个手段,只使用框架的通信功能在 View 之间发送消息的话,而将其他功能全部抛弃的话,直接使用事件更好,那还套一个 MVC 框架就是在没事找事了。

    MVC 关键还是在于代码逻辑的分配,通信只是个附赠品而已。要了附赠品而扔了原来的商品——咱们买的不是小浣熊干脆面,对吧。

    但是如果你的目的就是赠品,其实也没什么。比如你就是想用的这个通信框架来发发消息,就不打算用它的 MVC,又或者 MVC 部分是自己实现的。那么我还是建议你把消息部分干脆也自己实现了,别人的始终没有自己的好。

  • 既然用了 MVC 框架,就不要图省事
  • 要清楚,松散耦合不仅仅是一个形式,目的在于减少模块间的联系。因此,如果你一方面在解除两个模块之间的耦合,一方面自己又没头没脑的将其他模块的内容耦合进来,就会使得你的行为变得没有意义。

    现在一些人一方面在硬套框架,一方面又图省事而随意引入其他类,就属于这样的行为。那些类是可以引入,但你这样做,框架本身的意义就没有了。要不你就不用框架,要不就别这样干,这里只能二选一。

  • 是 Mediator 知道 View 的一切,View 完全不知道 Mediator,而不是相反
  • 对于使用 pureMVC 的同僚们,我真是不明白你们到底是怎么把这个反过来理解成“View 知道 Mediator 的一切,而 Mediator 完全不知道 View”的,因为官方实例上写的很明白。估计是把 mediator 当成通信专用的模块类了吧。但是如果你放弃了 mediator 分离 View 代码的特性,只是用来通信的话,至少要保留原来的通信功能,就是让 Mediator 依然可以直接访问 View。否则既然 Mediator 是用来通信的,它却不能操作 View,结果还得设法和 View 再通信一次……

    pureMVC 要求“View 完全不知道 Mediator”是为了能够在不修改 View 的情况下更换 Mediator,但这种需求并不多(多的是在 Mediator 不变的情况更换 View,这个需要用接口或者条件判断解决),所以可以放宽点让他们互相引用,这样两者通信都能畅通。

    pureMVC 实现“View 完全不知道 Mediator”的方法是用 Mediator 直接去监听 View 的某个组件的鼠标事件。这只需要监听一次,也不需要传递消息。Mediator 存在的期间,按 pureMVC 的标准 View 应该是没有任何监听逻辑的。

扩展阅读

http://baike.baidu.com/view/31.htm

http://www.360doc.com/content/09/0804/08/163747_4655702.shtml

http://hi.baidu.com/5%B1%CF%D2%B5%D2%D4%BA%F3/blog/item/2a018366a08e54cde6113af7.html

http://hi.baidu.com/lwcandwo/blog/item/8fb4b3036d01eb8ad43f7cf4.html

http://developer.51cto.com/art/200904/120649.htm

Java.NETRuby语言 & 开发