/ as

ActionScript3中的插件模式开发:(一)导论

大约3年前的2012年1月,我挖下此坑。其实我也时时想把坑填上,不过总不得闲。近期大事逐一落停,我再次鼓起勇气续写旧篇。

时过境迁,这三年里业界巨变,Flash的光芒逐渐被HTML5掩盖,我也考虑要不还是另起炉灶先连载移动Hybrid应用的文章。不过“插件模式”作为软件开发范式,并不只适用于ActionScript,而且它作用巨大、效果明显,再加上相关文章不多,于是我决定,还是先搞定这边。

如果条件允许,顺便也写个HTML5版。

好了,正文开始。


(这一章大多是抽象讲述概念,比较枯燥)

关于插件模式

插件式开发是我非常喜欢的一种设计模式。此系列文章专注讨论如何使用它。

实际工作中,某款产品,经常因时机和场景不同,需要不同的功能。比如,视频播放器,有时候需要包含贴片广告、更多推荐的全功能版本,有时候只需要播视频的基础功能;再比如一款在线游戏,可能圣诞需要上线一个活动,春节又希望上线另一个活动,但是两个活动的逻辑和素材可能完全不同,而且活动下线之后,里面的逻辑和部分素材可能再也用不上了。

最简单的做法当然是开发一个“完整功能版本”,这个版本拥有所有可能用到的功能,通过外部开关选择性开放某些特定功能,规范用户行为,避免太多功能分散用户注意力。可惜,这样做的话,代码量快速膨胀,软件结构臃肿,单个类容纳的功能越来越多,行数也越来越多,维护起来越来越困难;与此同时,用户每次都要下载包含所有功能逻辑的代码,很多内容可能他根本用不到。不仅浪费用户的时间,也消耗我们的带宽。

而插件模式,或者说插件式开发,则刚好可以解决这些问题。

定义插件模式

首先必须说明,在我理解中,“插件模式”更接近MVC,是一种工程结构而不是解决某个具体问题的方案。所以不能用标准设计模式的标准来定义。

下面是我的定义:

  1. 既然叫插件,那就应该有插的地方,对吧,被插的就叫主体好了。
  2. 毫无疑问,主体可以独立完成核心任务
  3. 插件接入后,可以完成各种非核心任务
  4. 插件可以独立存在,比如在Flash环境下,可以单独编译成SWF文件
  5. 插件的业务逻辑是缺失的,没有主体的插件不能运行
  6. 主体会在特定位置留下开放的接口,广播特定的事件,但它对并不知道插件怎么使用,也不关心
  7. 插件则熟悉主体的各种开放接口和事件
  8. 通常来说,会由插件管理器管理所有插件的生命周期
  9. 插件管理器通常是单例
  10. 物理上,插件管理器通常存在于主体内部,比如在Flash环境下,是一个类,最后打包到核心SWF文件中
  11. Flash工程中,我们多半会使用Preloader加载业务SWF和素材,此时插件可以无缝融入
  12. 不同插件之间多半不互相联系,如果需要联系,则通过插件管理器预先开放接口来操作

我定义了三个组成部分:主体、插件、插件管理器。这三部分的关系就如同前文所述,相信大家一看就明白。

然而,如何划分核心任务和非核心任务,如何确定插件的粒度,很难给出明确的界定方式。只能具体项目具体分析了。

vs. 传统软件中的”插件“或是游戏中的”外挂“

本系列文中的”插件模式“和传统软件(比如Winamp)中的”插件“定义不同,和游戏中的外挂也不同。

传统软件或游戏中,用户将应用下载到本地,然后运行,开发者无法知晓用户的真实行为,所以只能任由用户自行选择插件来丰富服务内容或改善操作体验。

而在Web环境下,用户通过访问URL享受服务,我们通过访问路径、各种参数,可以准确判断他们的使用场景,继而将最合适的功能和内容推送给他们。整个过程用户完全不知情,但是能感受到良好的体验。

本系列的”插件模式“就是帮助用户更好地享受Web服务,帮助开发者更好的组织代码的一种方法

AS3中的插件模式

接下来,让我们从代码层面上,以ActionScript3为例,解析插件模式的做法。首先,我们可以得到一些指导原则:

  1. 插件管理器应该是单例,并且可以通过工厂模式来创建插件实例
  2. 每个插件也都是单例
  3. 最好有全局事件,以便插件捕获

接下来我通过伪代码来脑爆一下插件管理器插件的构成。

public class PluginManager {
  // 用来管理插件的加载、构造、销毁
  // 能够处理后加载的插件,比如显示加载进度
  // 处理通用错误
  public function createPlugin(pid:String, vo:PluginVO); //通过配置文件里的内容创建插件
  public function getPlugin(pid:String); //取得插件
  public function refreshPluginsData(obj:Object); //更新所有插件的数据
  public function removePlugin(pid:String); //移除插件
  public function destroyPlugin(pid:String); //销毁插件
}

public class PluginLoader {
  // 用来处理插件的加载、卸载过程
  // 初期我们不会用到它,我们会先把插件和主体放在一起
  public function loadPlugin(pid:String, vo:PluginVO); //加载插件
  public function unloadPlugin(pid:String); //卸载加载到的插件
}

public class PluginVO {
  // 单个插件的数据,如id、类路径、权限等等
  // 一般由配置文件生成
  public function get prop() {
    return _prop;
  }
  public function set prop(value:*) {
    _prop = value;
  }
}

public interface IPlugin {
  // 插件接口,由PluginManager来处理基础交互
  function install(vo:PluginVO)  //安装,通知它可以与外界进行交互了
  function fill(data:Object)  //填充数据,Install之后调用
  function refresh(data:Object) //更新数据,fill之后需要刷新数据时调用
  function destroy() //摧毁,确定插件不再需要的时候调用
}

差不多,凭空想象的话,插件系统应该是这个样子。然而实际上,业务逻辑不同,代码架构不同,插件模式实现起来可能是千差万别的。所以上面的代码仅供理清思路,不要深究。

总结

本章我对插件模式下了定义,探讨了它和传统软件中”插件“的不同,并简单设计了其中两个重要的组成部分:插件管理器与插件接口。

后面几章,我将通过开发视频播放器,一步步展示插件模式。