Flex设计模式思考——观察者模式

假设以下场景:

Application分为左右两部分:左边是文件夹树,右边是当前选中的文件夹下的所有子文件夹。
新增功能:
在文件树上和右边的列表面板上,添加对文件夹的CUD操作,当操作成功后,需要同时更新左右两部分的视图。(这里忽略CUD的用户界面)
  • 如果新增一个文件夹,新增成功后在文件夹树和右边的面板上都要显示这个新的文件夹。
  • 如果修改一个文件夹名,成功后两边的视图都需要更新这个文件夹的名字。
  • 如果删除一个文件夹,左边的文件夹需要删除不显示此文件夹,而且缺省选中上一级的文件夹,如果没有上一级文件夹,则选中顶层的第一个文件夹;右边的视图也需要相应的显示选中的文件夹下的子文件夹。


问题分解
假设左边的文件夹为PanelA,右边的列表面板PanelB,CUD操作对应的命令分别为CCommand,UCommand和DCommand(这里的命令代表了请求数据和处理数据的逻辑模块)。
于是问题可以分解为:
  1. PanelA和PanelB执行三条命令。
  2. 命令执行完毕后,PanelA和PanelB进行更新。
  3. PanelA上选中某文件夹,PanelB进行更新。

解决方案
执行命令
       先看分解问题1,由于PanelA和PanelB都需要执行三条操作(CUD),很自然把这三条操作封装成三个命令,也就是上面的CCommand,UCommand和DCommand。
       我们先看上面分解的问题1,一个视图需要执行一个命令,一个简单地方法是由视图直接create一个命令实例,然后执行。如下面伪码所示:
var cmd:ICommand = new CCommand();
cmd.execute(some parameters);
这样做的问题是视图必须引用命令的具体子类(如CCommand),当需要使用其它子类时,必须修改视图的代码。
所以,方案必须是把视图和命令解耦。这需要在视图和命令之间建立一种“协议”,“协议”传输执行命令所需的所有参数。Adobe有一个轻量级的解决方案, Cairngorm.

Cairngorm
Cairngorm是一个比较简单的微框架,基本思想是把命令的请求者(如视图)和命令的执行者分开。

       Cairngorm包括 CairngormEvent,FrontController和Command三部分,其中的 CairngormEvent就是上面说的“协议”,Command就是上面提到的三个命令,而对这两者进行管理的就是 FrontController.
由上图,PanelA和PanelB只需要构造对应的 CairngormEvent(一般做法是扩展 CairngormEvent类,然后 传输执行CUD命令所需的参数),并dispatch  CairngormEvent就可以了。 FrontController将根据事件的类型执行对应的Command。
       由于视图和命令分离了,如果创建操作需要使用新的命令(由CCommand改为CCommand2),只需要修改 FrontController中的映射关系即可。只要“协议”不变,无需修改视图代码。
//  FrontController代码
addCommand(CreateEvent.CREATE_FOLDER, CCommand);

// CreateEvent代码
public class CreateEvent extends  CairngormEvent
{
       // some parameters
}

// PanelA/PanelB代码
var evt:CreateEvent = new CreateEvent(some parameters);
evt.dispatch();
        
如何更新视图
       回到上面分解的问题2,当命令执行完毕后,视图需要作出更新。
       一个直观的方法是由命令直接调用视图的更新接口。此做法存在一个致命问题:
       命令必须要知道有哪些需要刷新的视图,如本文场景中的PanelA和PanelB,要达到这种效果,“协议”中必须传输PanelA和PanelB的引用,如果要增加一个刷新的视图,必须修改“协议”, 这和我们解耦的目的是南辕北辙的,也会增加代码的维护工作量。

传统的观察者模式
       对于此种情况,一般比较好的做法是利用观察者模式。观察者模式的本质是,将更新的主动权归还给更新的主体(如上面的PanelA),而更新的发起者只需要发出一个通知。
       现在来看一下传统的观察者模式能不能解决那个“致命问题”。首先看一张观察者模式的UML图:

经典的GoF的的观察者设计模式
       ConcreteSubject就是上面提到的命令(如CCommand),ConcreteObserver就是PanelA、PanelB,我们注意到,PanelA和PanelB都必须实现同一个接口Observer,而Subject必须要先attach这些Observer对象。也就是说,CComamnd必须要保存PanelA和PanelB的引用,尽管是以Observer接口的形式,并不知道具体类。要做到这一点,要不CCommand要通过协议知道视图的引用,要不视图要知道最终执行的命令。这些都不是我们想看到的。

改良的观察者模式
       我们把Subject抽象成一个类似于Mediator的Controller,如下图。PanelA和PanelB都需要在Controller中进行attach,CCommand执行完毕后需要通知更新时,不是直接调用PanelA/PanelB的更新接口,而是调用Controller的更新接口,这样,CCommand和视图就解耦了。

        先看Command侧的具体做法。Command需要通知Controller,需要两个信息:一个是更新信息,如创建文件夹成功后的附加信息(时间等);一个是通知的类型,也就是要让Controller通知哪些视图。这里我们可以仿造解决问题1的做法,利用一个“协议”,我把它定义为ResponseEvent。
public class ResponseEvent extends Event
{
       public var data:Object;
       public var type:String;
}
其中data就是更新信息,而type则类似于上面的 CreateEvent.CREATE_FOLDER,是一个事件的类型。而这个事件类型则是视图侧和Controller进行attach的关联。

显然,这里的Controller是一个单例类。下面的示例代码忽略的单例类的构成方法,而且使用了一个内置的 IEventDispatcher对象作为存储类。
public final class CommandController
{
   public function CommandController (single:Singleton) 
  {
   _eventDispatcher = new EventDispatcher();
  }
 
   private var _eventDispatcher:IEventDispatcher; 
 
  public function addEventListener(type:String, func:Function):void
  {
   _eventDispatcher.addEventListener(type, func);
  }
 
  public function removeEventListener(type:String, func:Function):void
  {
   _eventDispatcher.removeEventListener(type, func);
  }
 
  public function dispatch(event:ResponseEvent):void
  {
   _eventDispatcher.dispatchEvent(event);
  }
}
         这种方案和Cairngorm很类似,不同之处是:
  1. 没有FrontController,Cairngorm需要在FrontController中预先初始化所有的CairngormEvent和Command之间的映射关系。如果需要动态增删映射关系,需要知道FrontController的引用,比较麻烦。所以上面的做法是相应把FrontController底层的CairngormEventDispatcher拿出来用。这样就可以随时增删映射关系。
  2. 这样做还有一个好处,就是不需要实现Cairngorm的ICommand接口,直接使用回调方法。
         所以一般的做法是在视图中,通过 CommandController监听 ResponseEvent事件,而回调方法则在视图中。再结合上面的代码:
// PanelA/PanelB代码
CommandController.getInstance().addEventListener(CreateResponseEvent.CREATION_COMPLETE, update);

public function update(event:ResponseEvent):void 
{
     // 更新视图
}

// 命令代码,执行完毕后
var evt: CreateResponseEvent = new  CreateResponseEvent( CreateResponseEvent.CREATION_COMPLETE);
evt.data = data;
CommandController.getInstance().dispatch(evt);
当然,直接使用 Cairngorm的 CairngormEventDispatcher也可以达到相同的目的。

视图间的通讯
       最后分解问题3,是一个视图之间通讯的问题。GoF的做法是用中间者模式,但如果只是PanelA控制PanelB,则还是可以使用观察者模式,直接使用问题2的解决方法。

(全文完)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值