简单示例程序解读
A Simple Sample Application Explained
原文地址:
http://opensource.adobe.com/wiki/display/cairngorm/SimpleSampleApplicationExplained
译稿最后修改时间:2010-6-1 12:36
介绍
此文仅是烟水晶框架构建应用的一个小小示范。
展示的例子是MAX2009会议上的“用Flex框架构建数据驱动型应用程序”主题演示的一部分。客户端代码示例是以烟水晶架构为基础构建和扩展的。关于此次演示的更多信息请查看克里斯多夫克雷茨 的博客。
这里查看客户端代码
服务端组件:下载 安装介绍
为了运行示例程序,服务器端组件必须安装。但是对于理解本教程并非必须。
这个示例使用了欧芹程序框架,然而本教程不会深入探讨欧芹框架的特性而是代以烟水晶框架的指导原则的特点。然而,此处概念上的实践可以应用于当前Fash平台上各种各样的应用程序框架。
包结构
包结构是重要的管理代码的途径。一中一致性的结构能够使得代码内的查找工作更容易进行,而且更容易理解各个不同部分之间的依赖关系。InsyncBasic 应用程序仅展示了烟水晶框架的建构层的代码是如何分离的。Insync代码包的后面,您可以找到下面这些代码包,描述了建构层:
表现层(关注于视觉外观和表现行为)
应用层(关注于表现的协同和架构组件)
域层(仅关注于问题域)
基础层(关注于例如 遥控服务或者UI框架)
这些包展示我们推荐的分层架构。
让那个我们从表现层开始深入这些架构层。
看,无脚本块的功能
刚开始,打开主程序文件,InsyncBasic.mxml,然后导航到线面显示的这个结构。您注意到的第一个东西是MXML可视组件内的一个没有脚本块的功能。这使得外观关注于尺寸和布局,使得在外观设计改进的时候更易阅读和更改。这是ToolBar组件:
<mx:Script>
<![CDATA[
[Inject]
[Bindable]
public var model:ToolbarPM;
]]>
</mx:Script>
<mx:Button
styleName="contactsAddButton" toolTip="Add Contact"
click="model.addContact()"/>
<mx:Spacer width="100%"/>
<mx:Label text="Search:"/>
<mx:TextInput id="searchBox"
change="model.search(searchBox.text)"/>
注意Inject标记。这是欧芹程序框架的部分功能,将ToolbarPM注入Roolbar 视图。
进入表现模型
无脚本块内的各种功能被提取到表现模型。每个MXML标记的可视组件拥有各自的表现模型(PM)。表现模型关注于用户和处理用户输入的状态和行为必需的表现数据。而且其与其他的表现模型乃至程序的其他部分完全无关。
这就是ToolBarPM,它在客户端组件接受搜索事件之前去掉了用户输入的字符中的左侧和右侧空格
public class ToolbarPM extends EventDispatcher
{
[MessageDispatcher]
public var dispatcher:Function;
public function addContact():void
{
dispatcher(ContactEvent.newAddContactEvent());
}
public function search(keywords:String):void
{
if (keywords == null)
return;
keywords=StringUtil.trim(keywords);
dispatcher(new SearchEvent(keywords));
}
}
一个表现模型不应该直接依赖一个可视组件;而应该是视图(可视组件)观察表现模型。这条联系在可视组件和它的通信表现模型之间的线路代替了绑定表达式和内联事件处置功能。这是ContactsList可视组件:
<mx:Script>
<![CDATA[
import insync.domain.Contact;
[Inject]
[Bindable]
public var model:ContactsListPM;
]]>
</mx:Script>
<mx:DataGrid id="list"
width="100%" height="100%"
dataProvider="{ model.contacts.items }" doubleClickEnabled="true"
itemDoubleClick="model.editContact(Contact(list.selectedItem))">
<mx:columns>
<mx:DataGridColumn dataField="firstName"
headerText="First Name"/>
<mx:DataGridColumn dataField="lastName"
headerText="Last Name"/>
<mx:DataGridColumn dataField="phone"
headerText="Phone"/>
</mx:columns>
</mx:DataGrid>
对于这个视图内完全没有逻辑错误处理的途径,真正的逻辑存在于表现模型内,这样更容易进行单元测试。由于异步组件的生命周期和对其他组件的弱依赖性(例如其他可视控件),直接对可视组件进行单元测试要困难的多。一个表现模型也会以将可视组件的API从MXML内可用的UI组件的大量API中抽离出来的方式简化组件。
组件之间的协调
一个程序内不同的组件需要协调来为用户提供有用途的特性。当在搜索栏内输入信息是,结果需要出现在下方的联系人列表内。这个行为是与表现关注相分离的,所以布局是能够独立修改的。
在InsyncBasic程序内,搜索操作是被ToolBarPM分发的了搜索事件调用的:
public function search(keywords:String):void
{
if (keywords == null) return;
keywords = StringUtil.trim(keywords);
if (keywords.length > 0)
{
dispatcher(new SearchEvent(keywords));
}
}
Commands内封装操作
搜索事件是在应用层接收处置的,一个Command以调用遥控服务并且将成功返回的结果放入一个域层内的域对象的形式调用RPC操作。
public class SearchContactsCommand
{
[Inject]
public var contacts:Contacts;
[Inject]
public var cache:IDataCache;
[Inject]
public var service:RemoteObject;
public function execute(event:SearchEvent):AsyncToken
{
return service.getContactsByName(event.keywords) as AsyncToken;
}
public function result(items:IList):void
{
contacts.addContacts(cache.synchronize(items));
}
}
这个Command遵从欧芹的动态Command特性。这个搜索事件的返回是由名为“execute”的方法处置的,而数据结果由名为“result”的方法处置。如果这个示例需要处置额外的错误反馈,指定另外一个名为“error”的方法。这个约定强制声明在欧芹上下文内的动态Command内部。
DynamicCommand type="{ SearchContactsCommand }"/>
也要注意一下IDataCache工具,这是烟水晶集成库的一部分。更多内容详见IDataCache,单击此处。
序列操作
Insync这个程序当新的联系人保存后刷新所有视图来显示最新数据。这使用LCDS的数据同步特性很容易处理,然而,在Insync程序内仅使用RPC操作,客户端需要在保存操作返回成功后手工调用搜索方法。这个简单的队列可以由应用层的专用对象来获得,就是RefreshSearchAfterSaveController。
public class RefreshSearchAfterSaveController
{
[MessageDispatcher]
public var dispatcher:Function;
private var lastSearch:String="";
[MessageHandler(selector="search")]
public function onSearch(event:SearchEvent):void
{
lastSearch=event.keywords;
}
[CommandResult(selector="save")]
public function onSaveComplete():void
{
dispatcher(new SearchEvent(lastSearch));
}
}
关于队列的更进一步的需求经常用于例如程序启动或者队列域行为,请查看任务类库。
保持小对象
注意前面示例中的小对象。从上面RefrdshSearchAfterSaveController或者下面的Contacts域对象内的真正只有一个责任。其所有方法引用所有的实例属性从而达成了功能的高内聚性。这将保证您的代码简洁,专注和更有弹性的修改。查看单责任原则以获得更多信息。
[Event(name="itemsChange", type="flash.events.Event")]
public class Contacts extends EventDispatcher
{
public static const ITEMS_CHANGE:String = "itemsChange";
private var _items:IList = new ArrayCollection();
[Bindable("itemsChange")]
public function get items():IList
{
return _items;
}
public function addContacts(items:IList):void
{
_items = items;
dispatchEvent(new Event(ITEMS_CHANGE));
}
public function addContact(contact:Contact):int
{
var index:int = -1;
if (items.getItemIndex(contact) == -1)
{
items.addItem(contact);
index = items.length - 1;
}
return index;
}
public function addItemAt(contact:Contact, index:int):void
{
items.addItemAt(contact, index);
}
public function removeContact(contact:Contact):int
{
var index:int = items.getItemIndex(contact);
if (index != -1)
{
items.removeItemAt(index);
}
return index;
}
public function removeContactAt(index:int):Contact
{
return Contact(items.removeItemAt(index));
}
}
此处就是InsyncBasic的基础示范的终结了。InsyncBasic仅仅展示了一个功能区,处理联系人。InsyncModularExtended 示例程序和教程关于与如何使用烟水晶框架推荐的其他功能区,例如信息传递和开销,以进一步了解烟水晶框架。
参考
马汀,R。单责任原则。
http://www.objectmentor.com/resources/articles/srp.pdf of OOD principles.
最后修改日期:星期日 五月 16日 06:20:27 PDT 2010年