设计模式实践(抽象工厂模式应用)—通信录的优化过程

类别:软件工程 点击:0 评论:0 推荐:
通信录的需求

1.         实现功能:

a)         联系人信息,包括:姓名、电话、手机等3项。(数据库中包含的其它信息暂不加入)

b)        向数据库中添加联系人信息,并能修改联系人信息。

c)        从显示列表中查看联系人信息。

2.         界面要求:

a)         主界面,主体为显示所有联系人信息的列表,菜单上分为:文件-->新建联系人,查看详细,退出。

b)        新建和修改界面,当点击菜单中的“新建联系人”项或“查看详细”时弹出,主体为“姓名”、“电话”“手机”3个输入框与相应的Label,下方为“确定”与“取消”两个按钮。当以修改界面出现时,“姓名”、“电话”“手机”3个输入框中显示相应的联系人的信息。当按下“确定”按钮时,本界面退出,主界面对新建或修改的联系人进行显示。按下“取消”键则不做变化。

如图所示:

                                 主界面                                                        新建/修改信息

未优化的设计

按照未优化设计,程序的UML类图如下所示:

从以上类图可以看出以下特点:

1.         界面类知道整个程序的所有内容,包括处理逻辑、数据库调用方法、数据库类型与结构等,并完成所有操作,包括数据采集、处理与结果显示;

2.         操作与数据库耦合紧密,编程时必须清楚数据库编程方法,如果数据库发生改变,程序所做的修改将很大;

3.         重用的界面必须清楚自己当前处于何种状态,即每进行一次操作时,都需要对标志位进行一次检查;

4.         如果界面发生大的改变,几乎所有的代码都要重写。

以上特点的解释是:

1.         主界面FormMain负责以下工作:

l         FormMain_Load()-LoadData(): 从数据库中取出当前已存在的联系人信息并显示在界面的列表上;

l         miNewPerson_Click()-LoadData(): 用户选择新建时,显示新建界面,并在该界面操作完成时根据返回值决定是否从数据库中取出联系人,在界面上重新显示。

l         miDetail_Click()-LoadData(): 用户选择更新时,显示更新界面,并将联系人的ID值传给它,当该界面退出时根据返回值决定是否从数据库中取出联系人,在界面上重新显示。

新建修改界面FormInput负责以下工作:

l         FormInput()-LoadData(): 根据传来的标志值决定本界面是新建界面还是修改界面,如果是修改界面,从数据库中取出对应于联系人ID的完整信息,显示在界面上;

l         cmdAdd_Click(): 根据标志位的值决定是调用新建方法Add()还是调用更新方法UpdateData();

l         Add()-IsDataValidate()-Execute(): 从界面上获得输入的联系人的信息,进行验证后存入到数据库中,使界面退出;

l         UpdateData()-IsDataValidate()-Execute(): 从界面获得修改后的联系人的信息,进行验证后存入到数据库中,使界面退出;

从以上功能可以看出,界面类必须知道所有程序的细节,不然无法完成操作。

2.         所有涉及数据库的操作都是直接操作,要负责连接与断开数据库,以及处理数据库异常,程序员的负担很重。如果数据库发生改变,程序员因为无法准确地定位修改点,必须在很长的界面代码中进行遍历,工作量很大。

3.         新建修改界面FormInput在初始化获得标志位,但进行操作时还要检查这个标志位,如果这种检查项很多,会造成很多的类似代码。

4.         如果界面发生改变,特别是转换界面风格时,所有的代码都要重新移植,工作量大。

由以上的分析可以看出,这样的设计造成程序的可扩展性差,维护的代价大。不是一个好的设计,需要进行优化。

优化设计

我们根据以下原则对设计进行优化:

第一条原则:界面类进行操作,只负责输入的收集与操作结果的显示。

第二条原则:输入输出相同,内容功能不同的操作要进行抽象,并由工厂模式进行选择。

第三条原则:操作逻辑控制与具体操作分开。

第四条原则:配置信息与数据实体独立出来。

根据第一条原则,可以将FormMain与FormInput的界面与操作分开,所有界面的事件都交给各自的事件处理类进行处理,两个事件处理类为MainFormEventHandler和FormInsUpdEventHandler。界面中只保留对信息的收集与处理结果显示的代码,其余的代码都放入对应的事件处理类中。这样FormMain类中只保留Form_Load(), Add_Click(), Update_Click()等3个方法,FormInsUpd类中只保留FormInsUpd(), Form_Load(), OK_Click(), Cancel_Click()等4个方法。

根据第二条原则,当新建/修改界面进行新建或修改操作时,都是输入联系人的信息,返回该操作是否成功的bool值,因此Add()和UpdateData()可以进行抽象,抽象出接口IInsUpd,内含方法Execute()的定义,由类ExecuteInset与类ExecuteUpdate实现该接口,并由其工厂类InsUpdFactory决定具体返回哪个实现类的实例。

根据第三条原则,接口IInsUpd的实现类不需要直接执行数据库操作,只需要规定数据库操作的逻辑,即访问哪个数据库,访问哪个表以及做何种操作(插入还是删除?)等,直接执行数据库操作的类是OleDbInsUpd。

根据第四条原则,将联系人的信息组装成Person类,另有规定新建/修改界面类型的枚举类ExeType,并将连接字符串放入DataConfig类中。

此外,我们还有进行验证数据有效性的类ValidatePerson和进行界面初始化时对界面信息进行填充的类Select,我们还将Main函数放入专门的程序入口类Enter中以减小可执行文件的尺寸。

通过以上的工作,我们基本完成了对通信录的优化。UML类图如下:

进一步优化

从一面的类图中,我们可以发现

1.         Select类自己既规定逻辑又直接操作数据库,不符合第三条原则,因此需要将它的功能分开,将数据库操作功能合并到类OleDbInsUpdDel类中;

2.         ExecuteInsert与ExecuteUpdate都要使用ValidatePerson类的校验方法IsValidateData(),为避免零碎,应该将该方法合并到它们的父类上去,但接口没有实现,只能在接口中规定该方法,再只能用一个抽象类实现该方法,再由ExecuteInsert与ExecuteUpdate类去继承抽象类。

针对这两个发现的问题,对类又做了修改,并添加了构件信息后,得到如下UML类图:

从上面的图上可以清晰地看出,整个程序的结构分为6层,即:

界面层,包括FormMain1, FormInsUpd;

界面事件处理层,包括MainFormEventHandler, FormInsUpd;

工厂层:包括IInsUpd, InsUpd, InsUpdFactory;

逻辑层:包括Select, ExecuteInsert, ExecuteUpdate;

执行层:包括OleDbExecute;

配置层:包括ExeType, DataConfig, Person;

可以归纳各层的功能为:

界面层:想要干

界面事件处理层:负责干

工厂层:由谁干

逻辑层:怎么干

执行层:我来干

配置层:工作时用到的参数

扩展的实现

检验程序是否具有良好的扩展性是看是否符合开闭原则,即对扩展开放,对修改关闭。

优化设计后,我们可以很容易地对程序做如下扩展:

1.         替换界面。例如要替换主界面,只需将MainFormEventHandler作为替换后主界面的字段,即可获得原有主界面的功能,移植很方便。

2.         扩展界面功能。例如要扩展新建/修改界面,增加一个删除功能,从图上可以看出界面的状态由枚举类ExeType决定,而由新建/修改界面衍生的并依赖ExeType类的类只有FormInsUpd和InsUpdFactory,而FormInsUpd只是传递ExeType值,并不做实际操作,只有InsUpdFactory需要明白ExeType值的意义,所以扩展点就在这里。实际扩展时,只要再从InsUpd抽象类继承一个ExecuteDel类完成删除功能,再在InsUpdFactory中增加决定代码即可。

3.         增加或减少数据库的字段。与数据库字段相关的类是Person类,如果要改变字段,只需查找与Person相关的类即可准确定位需要修改的代码的位置,且修改的代码不会对其它代码造成影响。

重用的实现

从上面的图上可以看出,不依赖其它类的类有:

l         接口IInsUpd;

l         枚举类型ExeType;

l         数据库配置类DataConfig;

l         联系人描述类Person;

l         数据库操作执行类OleDbExecute。

这些类只被别的类使用,自己却没有使用别的类,因此它们的独立性较强,但进一步分析后,发现:

l         枚举类型ExeType依赖于本程序界面类型共用的种类;

l         数据库配置类DataConfig依赖于本程序所有使用的数据库的种类与数据库名;

l         联系人描述类Person依赖于数据为对联系人信息所做的规定。

因此这三个类并不是完全自由的,无法脱离本程序。而接口IInsUpd与类OleDbExecute则彻底与本程序无关联。这样在以后的数据库操作中,我们就可以重用接口IInsUpd与类OleDbExecute。

本文地址:http://com.8s8s.com/it/it33128.htm