授人以渔,而不是授人以鱼。如果你能坚持看完前面三篇(1,2,3)还没有晕菜,或者甚至还有些疑问想要深究一下的,那么这篇文章就是为你而写的——关于这个工作流程和开发框架的来龙去脉。
这个开发框架的建立来源于Jesse Warden的这篇文章。通过它让我看到了Gaia与Robotlegs相结合的可能性。一般的Agency/Studio网站用Gaia框架已经足够了。但是引入MVC模式能够让开发架构更为清晰,方便进行更复杂的大型团队开发。随后再引入AS3 Signals基本是必然的,因为这个信号机制比Flash自带的Event好得多,就算不用这套框架你也应该尝试一下。
问题主要集中在于Gaia Page的职责划分问题:Page应该是MVC中的那个角色?或者Page甚至应该是一个完整的MVC模块?
如果说按模块化开发的结构来考虑,每一个Page可以看作一个独立的模块。也就是说每一个Page都应该有一个独立的Module Context。但是很多情况下我们所做的网站开发没有那么大的复杂度。如果遵照这种划分思想,只会造成无谓的类增加,凭空增加了维护的难度。所以权衡之下,我不推荐这种做法。有兴趣的朋友可以看一下Robotlegs模块化开发的扩展。
于是我们回到只有一个Main Context的情况下来考虑。在这里,Page可以是一个View(视图),也可以是一个Mediator(中介)。
Jesse Warden的文章中使用了前者,通过IPage接口的映射规则来启动Robotlegs自动中介的功能。
//当pages.AboutPage的实例被添加到场景上的时候建立一个AboutMediator作为其中介 mediatorMap.mapView("pages.AboutPage", AboutMediator, IAboutPage);
问题是使用这种中介机制,Page作为视图本身不应该处理事件。所以Gaia模板中的onTransitionIn, onTransitionOut和onDeeplink都成了摆设,这些事件应当被转发到对应的中介中去做处理。基于这种方式必须:
- 修改简化Gaia默认的Page模板。
- 建立一个扩展的PageMediator作为Page中介的基类,监听Page的几个默认事件。
后来我又尝试了第二种方向:把Page这一层直接作为中介,把真正的视图类归到views包下面。这种做法抛弃了Robotlegs的自动中介机制(不去继承Mediator),因为Gaia框架会负责加载和实例化Page。
通过Context的viewMap来设定注入规则,使得Page在被加载后能获取到对应的依赖关系(单独编译Page时要保留metadata的原因)。
//当属于pages包内的类的实例被添加到场景上的时候进行依赖注入 viewMap.mapPackage("pages");
同时,Page还要负责实例化真正的视图类(View)。
view = new AboutView(); view.init(); addChild(view);
这种做法让Robotlegs的自动中介功能成了摆设,而且中介是一个可视对象的子类也有点不符合逻辑。
综合比较下来,我还是选择了把Page当作中介的方式,手动去实例化视图。这种做法在不太复杂的开发中能够减少类的数量,相对而言比较符合我原先的开发习惯的。
我们再来看看Robotlegs的注入功能是如何实现?答案是通过监听ContextView的Event.ADDED_TO_STAGE事件,Robotlegs会判断是否为加到主场景上的每个实例进行依赖注入或者做中介映射。这点也是依赖注入框架被批判最多的地方,大量无意义的监听会消耗程序性能。特别在游戏开发等对性能需求严苛的环境下,这种浪费是致命的。
这点也是我没有采用Robotlegs自动映射中介功能的原因之一。由于我的Page中介不依赖于Robotlegs,所以可以很方便的关掉这种监听带来的性能损耗。具体做法是:只需当Page被加载的时候,打开监听,由Robotlegs完成注入;当Page加载完毕,关闭监听。
// MainMediator.as override public function onRegister() : void { Gaia.api.beforePreload(enableViewMap); Gaia.api.afterPreload(disableViewMap); } private function enableViewMap(event : GaiaEvent) : void { viewMap.enabled = true; } private function disableViewMap(event : GaiaEvent) : void { viewMap.enabled = false; }
当然更适用于普遍情况的解决方案应该是:只有当需要被注入的对象实例化时,由一定的机制来通知系统进行注入的操作。关于这种机制的实现,有兴趣的朋友可以看看eidiot的LazyMediator。
我们再来看看信号机制带来的改进。
首先要在Robotlegs中使用Signals,需要CommandSignal或者SignalExecutorMap。
Joel Hooks的这篇文章提供了一个示例。通过以下代码把信号映射为对应的命令:
signalCommandMap.mapSignalClass(AddFoodItemToOrder, AddFoodItemToOrderCommand);
在中介类中注入信号,就可以使用它去触发对应的命令了:
[Inject] public var addItem : AddFoodItemToOrder; // 注入 ... addItem.dipatch(itemType) // 触发
这种方式非常直观地表现了中介具体关心哪一种信号类型,但是相应地需要对每一个信号都去建立一个类。另一种做法是使用统一的SignalBus静态类来管理信号,比如:
package { public class SignalBus { // 定义一个Signal常量 public const addFoodItemToOrder : Signal = new Signal(FoodType); } }
映射命令:
// signalBus是SignalBus类的实例 signalCommandMap.mapSignalClass(signalBus.addFoodItemToOrder, AddFoodItemToOrderCommand);
在中介类中:
[Inject] public var signalBus : SignalBus; // 注入SignalBus实例 ... signalBus.addFoodItemToOrder.dipatch(itemType) // 触发
这样做的好处是可以少建立一些信号类,在我的开发模板中采用的是这种做法。你也可以查看使用这种方式的一个例子。
在开发框架之外,用Ant来进行一些批处理的工作也是这套工作流的亮点之一。如果你对Ant还不是很了解,建议你抽出点时间学习一下,对提高工作效率很有帮助:
- MY WORKFLOW WITH ANT AND FDT
- FDT and ANT | A User’s Guide
- FDT 3: You’re Gonna Need Some Ant With That Sauce
写在最后:整理完这套框架以后,我的开发思路清晰了很多。通过不同模块的划分定义,在开发团队中,可以有人专门去研究表现层交互效果,有人专门来处理数据的处理。有了框架以后,再忘掉框架,深入到不同层面的细节,去用技术实现自己的创意吧!













I really like your RobotGaiaAnt, but the folder structure I am using is different than yours.
It would be a nice feature if we can customize the folders structure.
yes, you can customize these properties in build.xml:
<property name="src" location="src" />
<property name="lib" location="lib" />
<property name="bin" location="bin" />