本文主要介绍本人再一个项目中应用设计模式思想的一些思路和心得,有使用不正确或不妥当的地方欢迎来信讨论。文章分为上、中、下三篇,上篇首先简单介绍了软件背景和基本业务流程,然后根据业务流程推导出策略模式中两个关键部件——可独立变化的“算法”和固定不变的“客户”调用结构的识别思路,以及给出了“算法”的接口模型。中篇主要介绍策略模式中另外一个重要部件——程序中“客户”调用结构的设计和代码。下篇主要介绍结合使用工厂方法、抽象工厂和单例模式创建具体“算法”的思路和架构代码。
最近公司正在搞一个生产管理的项目,涉及到后台管理系统、条码系统和前端手持应用,我所在的Team负责开发一套适合客户公司管理人员适用的PDA前端的应用程序。软件虽然很小,但是其业务流程相当有特点,是练习《设计模式》中几种思路的好项目。
首先简单介绍一下软件的功能和业务流程。该应用程序由VS2005(C#)开发,运行在WindowsMobile2003系统上,通过HTTP的方式与服务器通讯。服务器端由Servlet提供对客户端的通讯支持,该服务器端开发环境为Eclipse+WebLogic。PDA应用程序所实现的功能主要有产品入库、产品出库以及盘点作业三种。当成品下线以后,服务器端会从ERP系统中获取成品入库任务单,经过服务器端合并和过滤等预处理后,将任务单推送到手持终端上,操作人员得到产品入库的通知,持手持终端到达需入库产品附近,根据产品选择对应的任务单,在选择任务单中对应的任务明细,弹出扫描明细对话框。操作人员依次扫描需入库产品条码、数量等信息,当所有信息扫描结束后,操作任务单击“提交信息”按钮将信息发送到服务器端接口,系统将与任务单上信息进行比较,如果相符则发送到ERP系统进行后续流程,否则退回给PDA重新进行操作。其它两种操作流程基本相同,首先获取任务单,选择任务,选择任务明细,依次扫描产品条码信息,将扫描数据提交给服务器,如果是出库任务,服务器会验证扫描信息是否与任务单相符,盘点任务则无需验证。界面如下
主菜单:
选择任务后:
选择任务明细后:
从业务流程中可以看出,三个任务在流程上都是相同的,即选择功能——>下载任务单——>选择任务单——>下载任务明细——>选择明细——>扫描相关信息——>提交数据,所不同的只是上述功能的具体实现。流程相同,具体实现不同,我们自然而然的就能想到一种模式——策略模式。策略模式的核心思想是封装一组算法,每个算法都独立成为一个模块,继承自统一接口,并且可以互相替换,根据为客户代码提供的算法不同而使客户端任意改变功能,而无需客户代码做任何变动。也就是说算法与使用这个算法的代码可以独立变化。有关策略模式的具体实现这里不展开说明,还不了解这种模式的朋友可以先去查阅相关资料。
使用策略模式首当其冲的问题是确定那些是变化的,也就是所谓的“算法”,那些是固定不变,也就是使用这些算法的“客户”。在本例中,所有任务都遵从选择任务到提交数据这一系列流程,因此操作流程都是相同的,显然这是不变的,所以操作流程为“客户”。而具体针对每个功能而言,虽然有相同的流程但是流程的每个环节实现的方式不同,比如所有操作中都有下载任务单这个过程,但是任务单的结构不同,解析方式不同,在界面上的布局也不同,因此流程的实现是变化量,即“算法”。
找到了“客户”和“算法”,下一步就要看“客户”和“算法”分别该在哪些位置实现。首先来看“客户”,我们说了本例中的“客户”实际上业务流程,而业务流程实际上就是操作员完成某一任务的过程,操作员完成任务的过程中与PDA软件进行交互的部分实际上就是业务流程的体现。那么看下操作员在完成任务的过程中都做了什么操作,在主界面选择一个功能——选择任务单——选择任务明细——扫描相关信息——提交信息,明显的一个特点,所有与业务流程有关系的操作全部都在界面上,因此,基本可以确定,“客户”代码实际上就是界面上代码。显然,我们希望界面上处理操作流程的部分不动,通过传入不同功能所对应的模块来实现不同的操作。那么,我们所需要实现的功能就是“算法”了。下面给出“算法”也就是所用操作都要遵循的接口定义。
{
void GetTaskOrder(); //取任务单
void GetTaskOrderDetial(PDA_YAK_DLL.Message.Response task); //获取任务单下任务细节列表,task为向服务器发送的任务单报文
void UploadTask(Message.Response task,string strLogisticCode,string strQty,string strWh,string strRemark) ; //上传任务,task:需要上传的任务单报文,strLogisticCode:产品条码,strQty产品数量,strWh:储位,strRemark:注释
void FinishTask(Message.Response task); //完成任务
System.Data.DataSet GetDataSetForShowDetail(Message.Response detail); // 返回根据detail列表装配并返回一个数据集对象
System.Data.DataSet DataSetResponse // 以数据集的形式获取操作结果列表
{
get;
set;
}
System.Collections.ArrayList ResponseList //以队列表的形式获取操作结果列表
{
get;
}
}
接口定义了取任务单、取任务单明细,上传扫描信息,结束任务,获取操作结果等一些列行为,这些行为是入库、出库、盘点等功能共有的操作,所有这些功能都需要实现接口的这些方法,这样,策略模式中能够独立与“客户”变化的“算法”的基本模型就设计完毕了。由于派生类中会有公共成员,接口中不能对方法有任何实现,因此,我们首先从接口继承出来一个抽象类,所有的公共成员函数和方法都在该类中声明和实现。抽象类定义如下:
///抽象操作
///</summary>
abstract public class Operation:IOperation
{
#region基类成员
//……
#endregion
#region索引器
/**////<summary>
///获取或设置操作用户
///</summary>
public PDA_YAK_DLL.Message.ResponseUser User
...{
get {
//……
}
set {
//……
}
}
#endregion
#region构造器
public Operation()
{
// ……
}
public Operation(PDA_YAK_DLL.Message.ResponseUser user)
{
//……
}
#endregion
#region基类方法
////<summary>
///根据ds数据,创建服任务列表对象
///</summary>
protected virtual void BulidResponseList(System.Data.DataSet ds, string strKeyword, Type objType)
{
//……
}
#region接口方法
#region抽象方法
public abstract void GetTaskOrder(); //取任务单
public abstract void GetTaskOrderDetial(PDA_YAK_DLL.Message.Response task); //获取任务单下任务细节列表
public abstract void UploadTask(Message.Response task, string strLogisticCode, string strQty, string strWh,string strRemark); //上传任务
public abstract void FinishTask(Message.Response task); //完成任务
public abstract System.Data.DataSet GetDataSetForShowDetail(Message.Response detail);
#endregion
public System.Data.DataSet DataSetResponse // 以数据集的形式获取操作结果列表
{
get...{return dsResponse; }
set ...{ dsResponse = value; }
}
public System.Collections.ArrayList ResponseList //以队列表的形式获取操作结果列表
{
get ...{ return listResponse; }
}
#endregion
#endregion
}
中篇将介绍体现不变的业务流程——“客户”,即界面模块结构的设计思路和架构代码。