本系列文章将在新博客更新,链接在此,请移步阅读。
上次我分析了插件模式的一般特征,并且对主体应该具备的功能进行了设计。这次,我尝试拿具体的案例来分析如何设计插件,如何使插件和主体进行简单的联动。
先看例子:视频播放器。
视频播放器非常常见,就拿我以前做的播放器当例子,请看下图:
上方是最主要的部分——视频播放窗口,下面是控制面板,如此便组成了最简单的播放器。然而实际生产当中我们还会遇到各种各样的特殊需求,比如:片头广告、(播放完毕后)更多推荐、(时间轴上)关键点预告等等;这些需求,在不同的应用场景下又会呈现出不同的组合。比如,当作为页面视频广告时,需要监测有多少用户观看,观看了多长时间,而不需要其它增强功能;当嵌入到专门的视频文章页时,就需要将片头广告、更多推荐都包含进去;当被某个小网站借去改换外观重新上线时,只需要保留基本的播放功能就好,因为没有专人来维护其它内容。
如此这般因时间、地点不同而对产品功能产生了不同的需求,就适用插件模式了。要应用插件模式,首先咱们得区分主体与插件。
主体
对于播放器而言,主体自然包括“可视的视频主体”和“视频播放的基本控制器”两部分。具体到这个产品,我把外部参数作为“配置信息”也当成主体的一部分包含进来。于是主体就包含三个部分:
- 视频主体
- 基础控制面板(包括播放、暂停、音量调节等等)
- 配置信息
这三个部分互相关联,具有较高的耦合度,如下图所示。配置信息用来提供影片的相关信息(这里只标出getMovieSrc的方法),提供给视频主体读取;控制面板利用视频主体提供的方法来控制视频的播放、暂停等操作,并根据视频头来显示播放进度。
插件
相对来说,插件的构成比较自由,每个功能都可以独立成插件,于是统计一下需要,就归纳出如下这些插件:
- 片头广告
- 缩略图占位符
- 时间轴关键点提示
- 更多推荐
- 统计
- 画面热区
鼠标划过,浮现半透明的层,显示一些信息 - 关键点编辑插件
- 热区编辑插件
插件的复杂度跟功能有关,有些插件的逻辑和素材很复杂,比如4;有些则很简单,比如1。插件的复杂程度和它所处的位置无关,再复杂的插件也是插件。
插件的逻辑彼此不同,这里也不再画图了。
联动
回忆一下上一篇文章,关于插件模式的架构:主体 <=> PluginManager <=> IPlugin。一直到现在我都没有提PluginManager,似乎前后矛盾的样子。其实在这个案例当中,我并没有使用它。为什么呢?我考虑过以下几点,把插件和主体放在了一起:
- 全功能播放器的体积不过几十K(包括可视素材),拆成更小的模块,会加重用户加载的负担
- 视频并非网站主营业务,所以也没有一个针对视频的配置后台,拆开之后会给其他同事部署swf带来问题
- 播放器的功能虽然很多,但相对固定,不太可能有即时加载的需要。
最终方案里,PluginManager的功能由文档类来继承。同时,在主体三大类当中,不体现任何插件的内容,插件存放在plugin包中,这样就实现了物理区隔。于是,总的指导思想不变,只是表现形式稍微变了下,插件模式的优点依然保留。
最后插件接口IPlugin是这个样子:
现在,我拿最简单的“缩略图”来示范插件的加载和联动。因为文档类即PluginManager,所以在其中直接声明插件实例就可以:
.... var thumb:ThumbnailPlugin = new ThumbnaiPlugin(); thumb.install(this); // this为此插件的父容器 thumb.fill(video, controller, data); ....
在ThumbnaiPlugin这个类中,我们需要侦听controller的PLAY事件,以便适时的移除插件,相关代码是这样的:
public function install(container:Sprite):void { container.addChild(this); } public function fill(video:IVideo, controller:IController, data:IData):void { load(data.getPluginData().thumbnail); controller.addEventListener(PlayEvent.PLAY, playHandler); } private function load(url:String):void { // 加载并显示图片 } private function playHandler(event:PlayEvent):void { if (parent != null) { parent.removeChild(this); } controller.removeEventListener(PlayEvent.PLAY, playHandler); }
插件从IData当中取到自己需要的缩略图路径信息,加载并显示缩略图。当用户单击播放按钮开始播放时,插件会侦听到PlayEvent.PLAY,并移除自己。
总结
从始至终,主体并不关心插件的存在,插件从主体中取得自己关心的时间点和数据,执行自己的逻辑。
接下来,我会更进一步,探讨那些复杂的插件该如何完成任务,以及主体该如何配合它(们)。
欢迎吐槽,共同进步