设计模式的实际应用——在C#中解决单客户端窗口数据并发问题

http://www.uml.org.cn/sjms/201010113.asp

一、 问题引出

在VS2008环境下使用C#语言进行WinForm窗口开发时,大多数情况下我们都会使用弹出式窗口进行开发。

例如:

TestForm form = new TestForm();

Form.ShowDialog();

另一种窗口打开的方式为非弹出式,例如:

TestForm form = new TestForm();

Form.Show();

这里我使用“弹出式窗口”进行名称的统一,这种窗口的优点是:单线程窗口,十分便于程序员开发,并且在同一系统中的窗口不需要考虑其数据并发问题,十分方便数据管理。因为用户只能使用当前打开的窗口,换句话说就是我们必须关闭当前窗口才能操作其余窗口。可是这样开发出来系统十分不方便用户使用,其实以我们身边的有很多这样的例子,我们日常使用的很多软件为例,都不是弹出式窗口式窗口,比如:VS2008、SQL Server Management Studio Express、Microsoft Office Excel 2003等众多软件工具都是支持用户打开同时打开多个窗口,并且可以任意操作这些打开的窗口。我现在这个项目的系统升级中新需求里明确要求支持多窗口打开操作。虽然之前的系统中也使用了MDI窗口模式支持了部分窗口的非弹出式打开,但是为了避免数据并发产生的错误,所有在一级窗口打开之后的都是使用的弹出式窗口,此操作使得客户的系统易用性大大降低。但是所给的功能开发时间并不长,而且还涉及的二、三级窗口,甚至还有更深窗口,整个系统需要非弹出式窗口有上百的之多,还要解决数据并发问题,此功能添加可谓困难重重。

二、 问题描述以及期望结果

俗话说的好,解决问题的第一步就是描述问题。下面我就详细列出遇到的问题:

1) 在原有系统中,窗口之间会有数据上的交互,如:保存、数据传递。

2) 在原有系统中,窗口之间会有行为上的交互,如:重新刷新上级窗口中的列表。

3) 在原有系统中,由于使用了弹出式窗口,所以一个窗口只能有一个下级窗口,但是现在可以支持多个,也可以支持窗口的单个实例。此问题有需求而定,但是需要进行影响性分析和易开发性设计。

4) 当用户不按照窗口的打开顺序关闭窗口时,产生的数据混乱问题,例如:用户有窗口A打开窗口B,再在窗口B中打开窗口C,此时用户关闭窗口A。

5) 由于有百个以上的窗口,所以说如何修改的工作量十分巨大。

6) 如何使系统用所有窗口操作风格统一。

成功描述了需要解决的问题,我们的期望解决又当如何呢?我总结了一下包含两大类:针对窗口本身操作的期望、针对业务中需要实现的期望。

针对窗口本身操作的期望:

1) 希望所有的非弹出式窗口,在打开和关闭操作时和使用弹出式窗口的方法一致,但是都不希望每个窗口都为这个操作进行功能添加。

2) 当用户不按照顺序关闭窗口时需要有相关提示,并且为了解决数据并发问题,需要提供将所有当前窗口的后续打开窗口进行自定关闭的功能。(我在此处的设计思路主要来自win7系统进行关闭时,win7系统关闭方式)

3) 当窗口进行打开/关闭操作时,需要自动对其窗口进行注册/注销,设计相关标识来辨别系统中所有的窗口实例。

针对业务中需要实现的期望:

1) 由于业务需要,当前窗口关闭时需要刷新上一级窗口。

2) 由于业务需要,当前窗口关闭时需要保存上一级窗口。

3) 由于业务需要,当前窗口操作时需要向上一级窗口进行数据传递。

4) 所有打开的窗口可以进行最小化、填充最大化操作,并且还能进行此窗口实例的自定查找和标识辨别。

5) 由于业务需要,当前窗口关闭时需要的特殊操作。

三、 问题的分析和构思

现在我们来分析上述问题和针对期望结果进行的解决方案构思。我认为其设计主旨为:既要能同时打开多个窗口,又要使其操作流程与弹出式窗口一致,这样一来才能使其数据不混乱。至此,我们设计构思也就有了目标,为了满足上述分析结果,我使用了多个设计模式和继承体系来支持此方案构思。

四、 解决思路中应用到的设计模式

在此之前,我给朋友们简单介绍一下其中主要的模式。

1、组合模式:将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性。

2、迭代器模式:提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示。

3、职责链模式:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

4、模板模式:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。TemplateMethod使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

5、单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

6、状态模式:允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。

五、 解决方案定义(对象定义和分类、接口定义)

现在我详细描述一下设计方案,首先我们先看一下概念模型。(虽说这么小的功能无需使用概念模型,不过为了描述清楚此设计的全过程所以我把概念模型摆了出来)。

领域模型

我来一一对图中的类做一个概述:

1、BasicForm

窗口超类,用此超类来封装所有系统中的子窗口的统一操作。

2、IObtain RegisterBasicFormList

获得系统中所有打开的窗口实例集合的实例接口

3、RegisterBasicFormList

获得系统中所有打开的窗口实例集合的实例实现

4、PrompMessageForm

在关闭当前打开的窗口前,弹出其所有后续开打窗口的提示窗口

5、ShowType

显示显示类型的枚举对象(包含Show、ShowDialog两个类型)

6、IShow

非弹出式窗口的打开接口

7、IShowDialog

弹出式窗口的打开接口

8、继承BasicForm的子类

系统中需要使用的所有Form窗口。

9、IReflesh

刷新接口

10、ISave

保存接口

11、IDeliver

数据传递接口

12、SystemRegister

系统中需要注册的全局变量

由上述的概念模型我们看到了,当BasicForm的子类在继承其超类的过程中,根据其业务的需要实现IShow、IShowDialog、IReflesh、ISave、IDeliver。而在BasicForm这个积累中,由于BasicForm是继承Framework3.5中的Form,所以我们可以运用其本身的特殊性来执行一些固定的窗口事件。比如:Load事件、FormClosing事件、FormClosed事件。我们用更为详细的类图来展示:

上述UML类图讲述的是窗体超类的细节接口和继承超类的子类操作:子类的操作十分简单,其代码如下:

public partial class OneForm : BasicFormIRefleshISaveIDeliver
    {
        public OneForm()
        { InitializeComponent(); }
        public OneForm(string Identification, BasicForm ParentBasicForm)
        {
            InitializeComponent();
            base.Identification = Identification;
            base.ParentBasicForm = ParentBasicForm;
        }
        private void button1_Click(object sender, EventArgs e)
        {
            TwoForm form = new TwoForm ("窗体子类的实例名称,如:SonForm"this);
            form.MySave = this;
form.MyReflesh = this;
form.MyDeliver = this;
            form.ShowBasicForm();
        }
        public void Reflesh()
        {   //刷新操作 }
        public void Save()
        {  //保存操作 }
        public void Deliver()
        { //数据传递的特殊操作 }
}

根据数据的业务需要,我们实现其接口,进行需要操作的接口添加即可。

所有之前考虑的注册、打开、窗口控制等等所有操作都是在超类内部实现的。例如

需要实现的事件有:

1、在超类中的BasicForm_Load事件(private void BasicForm_Load(object sender, EventArgs e))中,需要实现注册、当前窗口的显示样式、位置、实例在注册表的检索、实例重复的话是提示还是新实例化、设置各种各样系统中需要统一的窗口属性。

2、在超类中的BasicForm _ FormClosing事件(private void BasicForm_FormClosing(object sender, FormClosingEventArgs e))中,需要实现在关闭当前窗口前根据业务需要进行的一系列子窗口的判断和各种提示操作。

3、在超类中的BasicForm_ FormClosed事件(BasicForm_FormClosed(object sender, FormClosedEventArgs e))中,需要实现按照业务需要调用IReflesh, ISave, IDeliver接口的功能,注销当前窗口在全局中的注册信息操作。

需要实现的公有方法有:

1、ShowBasicForm()方法:用超类中的Identification属性去全局的BasicFormList中去查找其实例是否存在?不存在的打开,存在的话根据业务需要进行打开现有实例,还是提示信息,还是打开另一个实例。设置部分窗口属性,以及this.Show()方法的显示操作。

2、ShowDialogBasicForm()方法:设置打开窗口前的属性,以及弹出式操作this.Show()方法的调用。

需要实现的接口方法有三类:

IReflesh, ISave, IDeliver三个接口是根据其业务需要而来的,一般使用情况为:

1、如果当前窗口是个列表类/一览类窗口,则当前窗口在调用下一级窗口时,需要实现IReflesh接口

2、如果当前窗口需要保存下一级窗口的数据时,需要实现ISave接口

3、如果当前窗口需要与下一级窗口进行信息交互等特殊操作时,需要实现IDeliver接口,此处需要使用子窗口的共有方法或者共有属性。

设计了那么多,其实就是为了能够尽可能的将子类修改降到最低,达到超类的封装,尽管本次系统升级中这样的窗口上百个,又要写代码编写手册,但是由于原有窗口修改为继承BasicForm超类操作十分简单。所以修改还是十分顺利的,多个人在短时间成功完成了此任务。最重要的一点是此后的调整更为简单,只要超类一变,整个系统的机制就随之变化,十分方便后期修改,即使时系统级的也可以做到快速调整代码。

六、 问题思考

不知道大家发现到没有,到此为止之前我介绍的模式在详细设计中一点都没有说到,这是为什么呢?其实这是我设计软件的一种设计方式。因为我们都知道,设计模式并不是一程不变的。我认为在进行设计的时候,并不需要特意的去套用自己会使用的设计模式,只不是在解决问题时,根据需求进行面向对象的分析和设计,在设计时尽量充分地使用对象的三大特征。然后在是设计模式对自己的设计进行一个验证。

1、组合模式:对于超类来说,ChildrenBasicForm属性可以是代表自身子类的一个节点集合,这也满足组合模式的使用环境。

2、迭代器模式:当我们在进行关闭当前窗口操作是,我们可以获得当前窗口的所有子窗口的集合,这个时候我们需要循环关闭此窗口的实例,此时我们将逐个关闭所有子窗口的对象,不需求要知道是哪个子类的实例,因为都是基于超类中的Close()方法来进行操作的。

3、职责链模式:当关闭当前窗口的子类是,由于当前窗口的子窗口可能还有后续子窗口,所以BasicForm _ FormClosing事件和BasicForm _ FormClosed事件将会继续关闭其子窗口,从而以链式的形式关闭当前窗口下派生出来的所有子窗口。

4、模板模式:我们可以看到无论什么窗口,都在子类执行IReflesh, ISave, IDeliver三个接口,但是我们可以根据其业务的需要来设计子类,使其根据其需要进行实现,但是在超类中IReflesh, ISave, IDeliver三个接口的操作调用是永远不变的。

5、单例模式:当我们根据业务需要要求系统中所有窗口都打开一个实例时,其实我们可以使用单例模式,当我们在注册列表找到此窗口的实例时,直接取出,不应在新创建实例。

6、状态模式:在我们调用基类的打开方法时(ShowDialogBasicForm()和ShowBasicForm()方法),对ShowType的两个枚举类型进行赋值,在打开方法中根据其枚举的哈希值或者枚举类型的值来选择窗口的显示形式是弹出式还是非弹出式。

七、 结束语

看了上述的思考,其实我们要表达的内容也出来了,我们在实际应用中,不应该为了使用模式和去使用,而是根据我们系统中业务的需要来使用,而且我们在设计一个系统完后,也可以根据设计模式在复查我们的设计,看它是否合理?设计模式对我们来说既可以是学习的一种工具,也可以是检查的一种参考。


阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭