Online Book Reader

Home Category

Beautiful RIA [22]

By Root 424 0
在窗口之间交互的时候,这种将主动权交给调 用方控制的方式,会给状态同步带来不少麻烦;如果你使用了本地存储,它越过存储层直接 与服务器交互的方式也会带来不少的不便之处。更好的方式是使用“3事件管理”。当然,如 果窗口之间导航不存在数据传递,基于 Navigator的方式仍然简单并且可用。

3 事件管理

事件管理应当是整个 RichClient/RIA开发中的最难以把握的部分。这部分控制的好,你的程 序用起来将如行云流水,用户的思维不会被打断。任何一 个做 RichClient开发的程序员,可 以对其他方面毫无所知,但这部分应当非常熟悉。事件是 RichClient的核心,是“一切皆异步” 的终极实现。前面所说的例子,实际上可以被抽象为事件,例如第一个,获取股票数据,从 事件的观点看,应该是:

 开始获取股票数据

RichClient/RIA原则与实践

 正在获取股票数据

 获取数据完成

 获取数据失败

看起来相当复杂。然而这样去考虑的时候,你可以将执行计算与界面展现清晰的分开。界面 只需要响应事件,运算可以在另外的地方 悄悄的进行,并当任务完成或者失败的是时候报 告相应的事件。从经验看来,往往同样的数据会在不同的地方进行不同的展示,例如 skype 在通话的时候这个人 的头像会显示为占线,而具体的通话窗口中又是另外不同的展现;MSN 的个人签名在好友列表窗口中显示为一个点击可以编辑控件,而同时在聊天窗口显示为一个 不能点击只能看的标签。这是 RichClient的特性,你永远不知道同一份数据会以什么形式来 展现,更要命的是,当数据在一个地方更新的时候,其他所有 能展现的地方都需要同时做 相应的更新。如果我们仍然以第一部分的例子,简单采用 runInAnoterThread是完全不能解 决这个问题的。

我们曾经犯过一些很严重的错误,导致最终即便重构都积重难返。无视事件的抽象带来的影 响是架构级别的,小修小补将无济于事。

事件的实现方式可以有很多种。对于没有事件支持的语言,接口或者干脆某一个约束的方法 就可以。有事件支持的语言能够享受到好处,但仍然是语法级别的,根本 是一样的。观察 者模式在这里很好用。仍然以股票为例,被观察的对象就是获取股票数据对象 StockDataRetriver,观察的就是 StockWindow:

StockDataRetriver {

observers: []

retrieve() {

try {

theData = ...//从远程获取数据

observers.each {|o| o.stockDataReady(theData)} //触发数据获取成功事件

} catch {

observers.each { |o| o.stockDataFailed() } //触发事件获取失败事件

}

}

}

StockDataRetriver.observers.add(StockWindow) //将StockWindow加入到观察者队列

RichClient/RIA原则与实践

StockWindow {

stockDataReady(theData) {

showDataInUIThread(); //在UI线程显示数据

}

stockDataFailed() {

showErrorInUIThread(); //在UI线程显示错误

}

}

你会发现代码变得简单。 UI与计算之间的耦合被事件解开,并且区分 UI线程与运算线程之 间也变得容易。当尝试以事件的视角去观察整个应用程序的时候,你会更关注于用户与界面 之间的交互。

让我们继续抽象。如果把 “获取股票数据”这个按钮点击,让 StockDataRetriver去获取数据当 作事件来处理,应该怎么写呢?将按钮作为被观察 者,StockDataRetriver作为观察者显然不 好,好不容易分开的耦合又黏在一起。引入一个中间的 Events看起来不错:

Events {

listeners: {}

register(eventId, listener) {

listeners[eventId].add(listener)

}

broadcast(eventId) {

listeners[eventId].observers.each{|o| o.doSomething(); }

}

}

Events 中维护了一个 listeners的列表,它是一个简单的 Hash结构,key是 eventId,value是 observer的列表;它提供了两个方法,用来注册事件监听以及通知事件产生。对于上面的案 例,可以先注册 StockDataRetriver为一个观察者,观察 start_retrive_stock_data事件:

Events.register('start_retrive_stock_data', StockDataRetriever)

当点击“获取股票数据”按钮的时候,可以是这样:

Events.broadcast('start_retrive_stock_data')

你会发现 StockDataRetriver能够老老实实的开始获取数据了。

需要注意的是,并非将所有事件定义为全局事件是一个好的实践。在更大规模的系统中,将 事件进行有效整理和分级是有好处的。在强类型的语言(如 Java/C#)中,抽象出强类型的 EventId,能够帮助理解系统和进行编程,避免到处进行强制类型转换。例如,StockEvent:

StockDataLoadedEvent {

RichClient/RIA原则与实践

StockData theData;

StockDataLoadedEvent(StockData theData); }

Event.broadcast(new StockDataLoadedEvent(loadedData))

这个事件的监听者能够不加类型转换的获得 StockData数据。上面的例子是不支持事件的语 言,C#语言支持自定义强类型的事件,用起来要自然一些:

delegate void StockDataLoaded(StockData theData)

事件管理原则我相信并不难理解。然而困难的是具体实现。对一个新的 UI框架不熟悉的时 候,我们经常在“代码的优美”与“界面提供的特性”之间徘徊。实现这 样的一个事件架构需 要在项目一开始就稍具雏形,并且所有的事件都有良好的命名和管理。避免在命名、使用事 件的时候的随意性,对于让代码可读、应用稳定有非 常大的意义。一个好的事件管理、通 知机制是一个良好 RichClient应用的根本基础。一般说来,你正在使用的编程平台如 Swing/WinForm /WPF/Flex等能够提供良好的事件响应机制,即监听事件、onXXX等,但一 般没有统一的事件的监听和管理机制。对于架构师,对于要使用的编程平台 对于这些的原 生支持要了熟于心,在编写这样的事件架构的时候也能兼顾这些语言、平台提供给你的支持。

采用了事件的事件后,你不得不同时实践 “线程管理”,因为事件一般来说意味着将耗时的操 作放到别的地方完成,当完成的时候进行事件通知。简单的模式下,你可以在所有需要进行 异步运算的地方,将运算放到另外一个线程,如 ThreadPool.QueueUserWorkItem,在运算完 成的时候通知事件。但从资源的角度考虑,将这些线程资源有效的管理也是很重要的,在“线 程管理”部分有详细的阐述。另外,如果能将你的应用转变为 数据驱动的,你需要关注“缓 存以及本地存储”。

4 线程管理

在 WEB开发几乎无需考虑线程,所有的页面渲染由浏览器完成,浏览器会异步的进行文字 和图片的渲染。我们只需要写界面和 JavaScript就好。如果你认同“一切皆异步”,你一定得 考虑线程管理。

毫无管理的线程处理是这样的:凡是需要进行异步调用的地方,都新起一个线程来进行运算, 例如前面提到的 runInThread的实现。这种方式如果托管在 在“事件管理”之下,问题不大, 只会给测试带来一些麻烦:你不得不 wait一段时间来确定是否耗时操作完成。这种方式很 山寨,也无法实现更高级功能。更好的的方式是将这些线程资源进行统筹管理。

RichClient/RIA原则与实践

线程的管理的核心功能是用来统一化所有的耗时操作,最简单的 TaskExecutor如下:

TaskExecutor {

void pendTask(task) { //task:耗时操作任务 runInThread {

task.run(); // 运行任务

}

}

}

RetrieveStockDataTask extends Task {

void run() {

theData = ... //直接获取远程数据,不用在另外线程中执行 Events.broadcast(new StockDataLoadedEvent(theData)) //广播事件 }

}

需要进行这个操作的时候,只需要执行类似于下面的代码:

TaskExecutor.pendTask(new RetrieveStockDataTask())

好处很明显。通过引入 TaskExecutor,所有线程管理放在同一个地方,耗时操作不需要自行 维护线程的生命周期。你可以在 TaskExecutor中灵活定义线程策略实现一些有趣的效果,如 暂停执行,监控任务状况等,如果你愿意,为了更好的进行调试跟踪,你甚至可以将所有的 任务以同步的方式执行。

耗时任务的定义与执行被分开,使得在任务内部能够按照正常的方式进行编码。测试也很容 易写了。

不同的语言平台会提供

Return Main Page Previous Page Next Page

®Online Book Reader