设计模式&重整技术运用实例

来源:百度文库 编辑:神马文学网 时间:2024/04/18 10:03:48

作者: 点空间  陈国生
Email:bruce123@edirect168.com日期:2003/09/06
设计计算机系统,最困难的部分是什么?也许你可以找出一大堆的答案,但真正最困难的部分是面对系统功能的改变,这是因为我们无法去预知这些改变。但或许你会说,那就尽量保持弹性,话虽如此,但我们如何知道要在什么地方保持弹性?但如果你的系统无法去应付这些改变,那么就只有走向重写一途,这无疑是一件让人沮丧的事。在面对这个问题,我们`必须学会用设计模式去思考,如果系统的设计已经僵化了,我们就让它柔软下来,如果柔软不下来,就把它冻结起来,如果原来设计弹性就很好,我们就继续扩充功能,没有解决不了的事,以下我们举个例子来说明这个想法。
首先,我们从一个设计模式说起:Facade 外观模式。我们来谈谈这个模式,这个模式的用途是将一个子系统内部的众多对象统一起来,产生新的接口出来。这让我想到我们中国有句成语叫:老干新枝。这个意义是,老干已经失去了活力,但为了生存下去,必须产生新的枝芽,这跟一个已经僵化的系统一样,我们无法期望从原来的枝长出叶子,所以只好另长新芽。
如果一个系统已经纠成一团,如左图般的状态,再继续扩充功能,或更动系统功能,那无疑的是雪上加霜,加速系统的走向死亡之路。由于系统内部的对象交互影响,只要移动部分的功能,Client 端的使用接口势必要跟着改变,继续使用这样的系统结构,终究有一天会土崩瓦解。重新去调整这个致命的缺点,又跟重新改写没有两样,常常这样让人陷入进退两难的窘境。
如果我们可以用设计模式去思考解决问题,如右图将原系统视为一个子系统,使用Facade这个样式,重新产生使用接口,将这混乱的局面控制,不再像癌症一样继续扩散开来,内部的调整不再影响Client 的使用接口,那么就可以让系统取得一个扩充功能的契机。系统控充功能与内部结构的改善,这是两个不同的方向,千万不要搅在一起,也不要同时去改变,这样会使问题更加的复杂,
接下来,我们来探讨一下什么叫重整?Martin Fowler 于书中定义它是一种「在不改变代码外在行为的前提下对代码做出修改,以改进代码的内部结构」的过程。如果说重整的目的不在于对既有的代码增加新功能,而只是不影响程序既有功能及正确的执行下,改善程序代码。那么对许多人而言,让他们感到相当疑惑 的是,这种行为是否能带来任何的益处?重整如果只是改善内部结构,那显然是无助于增加效益,所以重整的目的势必与往后的系统扩充功能产生紧密的关联。俗语说:休息是为走更远的路,所以没有重整,也就没有更远的路。而本篇重点是在改善结构跟扩充功能这两个想法紧密的结合在一起。也就是说在改变现有结构之后,进行系统功能扩充,根据现有的结构加以改善,以符合扩充的需要。试想,如果没有新的需求产生,需要扩充功能,又何必大费周章的去进行重整呢?但是从本篇的范例中,有些范例虽然未扩展功能,但经过使用样式设计进行重整结构的改善,几乎已经可以看见扩展新的功能的曙光,这是重整带来最大的好处。
俗语说:做衣容易,改衣难。这说明一件事,虽然做衣服跟改衣服,用的是同样的技术,但却是不同的思维,所以变成了会做衣服却不一定会改衣服。对设计系统而言,系统中任何一个变动,就像改衣服一样,学习克服这些变动,才能快速的反应变动。近年来软件工程的思维倾向贴近客户需求,拥抱改变,但这不光是口号而已,在这个思潮下,背后必须有强而有力的设计技术及思考模式所支撑,因此学习这些基本技术及不断的磨练你的思考模式,才能去符合这样的要求。本篇就是基于这样的想法,将设计模式与重整两个重要的基本技术加以结合运用,实现系统面对改变时,如何去反应变动,及如何让系统保持适当的弹性。
底下,对本篇所提供的实作范例,作简单扼要的说明:
范例一:运用Observer 观察者模式解开单元之间的耦合,是重整的典型范例,本范例着眼于系统结构改善,并未扩充新功能。
范例二:运用Template Method 样式设计数据库的异动交易控制,这个范例架构在原系统上面,将原来程序代码加以冻结,并扩展新功能。
范例三:运用Factory 工厂模式设计多人使用的权限控制,这个范例使用替代的方式,在不改变原有功能的前提增加了系统的弹性。
范例一
运用Observer 观察者模式解开单元之间的耦合
作者:陈国生
Email:bruce123@edirect168.com
【解决问题】
在Delphi 的对象中,TForm 是一个很抢眼的角色,因为在一个Form 当中,我们可以轻易的结合许多不同的对象一起工作,几乎是无所不能,但是在比较复杂的系统中,我们必须根据不同的功能,切割放在不同的Form 上面 ,或者说是在不同单元当中。当一个系统存在许多不同单元之后,我们要如何去让这些单元可以相互合作?使用USES 可以将两个不同单元视为一个单元来使用,但遗憾的是,这种设计方式往往会造成单元之间的耦合性过高,当一个单元变动之后,往往其它的单元也会跟着受影响,而整个系统也就因此牵一发而动全身了。
当然,我们可以透过使用接口让这个问题不至于过度的恶化,但仍无法确实的解决这个致命的纠缠。因为透过使用接口来运作,仍然必须USES这个单元,当这个单元被取消之后,仍然会影响其它单元的运作。
【面临问题】
也许,此刻你的程序已经陷入这样致命的纠缠当中,我们要如何才能去解开这个结呢?
【运用技术】
本范例将运用Observer 观察者样式,来解决单元之间的纠缠不清。首先我们先来看看这个观察样式定义:观察者模式定义对象间的一对多的相互关系,使得一个对象改变时,其它相关对象皆可获得通知并自动更新。这个模式里面,会有一个观察者,及一个以上的被观察者,当观察者的状态改变时,被观察者被主动的告之,而观察者负责通知,被观察者却不用知道观察者是谁。
从理解一个抽象概念的,到运用在解决问题上面,过程通常不是那么的容易。根据观察的样式提供的概念,其实Delphi 这个RAD 已经帮我们设计许多功能强大的对象,所以重要的是,我们如何去运用这个概念去解决问题,要把焦点放在解决问题上面。
观察者模式,有点类似Window里面的讯息传递机制,如果我们要实作这样的机制,那么必须要有一个可以储存这些讯息及事件的容器型对象。在Delphi 中有许多现成的对象,你无须费心的去设计这样的对象,只要找出可以实践这样想法的对象即可,本范例中,我们运用TActionList来实现,TActionList  是一个事件串行,现在就看我们如何善用这个对象,去达到讯息传递的目的了。
在下面的范例中,我们将实作一个横跨整个系统的观察者样式,解开单元之间的耦合问题。
【实作范例】
第一步,建立一个TDataModule,并在DataModule 1上面放置一个TActionList 的对象。
第二步,分别建立两个TForm,Form1及Form2 ,在Form1,Form2 各放一个TEdit  以供测试。
第三步,在TDataModule 增加一个属性,myValue:string。
第四步,在TActionLIst 增加一个TAction 对象,并写入控制事件:
procedure TDataModule1.Action1Execute(Sender: TObject);
begin
if Form1<>nil then  Form1.Edit1.text:=myValue;
end;
第五步,在Form2的Edit1的OnKeyPress 写入:
procedure TForm2.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
DataModule 1.myValue:=Edit1.text;
DataModule 1.Action1.Execute;
end;
第六步,在Form1激活Form2,然后在Form2的Eedit1 输入资料,此时可见Form1.Edit1 同步收到讯息了。
从这个范例中,可以将Form2视为是一个观察者,而Form1 是一个被观察者,当Form2的TEdit的值改变后,透过事件通知,改变了Form1的TEdit,然而Form2 并无USES Form1 ,也不知道Form1的存在,所以即使Form1消失了,仍然不会影响Form2的运作。
所以,从这个运作架构中,我们可以依系统变动的需求,不断的增加控制事件,却不会影响单元之间的关系。
【结        论】
在一个大的系统里面,控制单元之间的耦合性非常重要,以上述的范例来说,不论系统如何改变,各个单元都可以维持正常的运作,既相互合作又不会相互影响,其变动姓被有效的控制在一个事件串行当中,当单元变动时,我们很容易追踪,且不会扩散到整个系统。
范例二
运用Template Method 样式设计数据库的异动交易控制
作者:陈国生
Email:bruce123@edirect168.com
【解决问题】
什么是异动交易控制?简单的说,就是如何确保在资料改变的过程,可以顺利的完成,异动交易控制最主要的着眼点在于对交易失败的控制,试想一个交易,基于某种不可预期的原因造成程序产生错误,中途被迫终止交易,那之前已经改变的资料算不算数?如果不算数,那之前已经交易的资料怎么办呢?
举例来说,一张进货单可能要同时更新单据档及库存档,如果中途失败,可能造成单据文件有资料而库存文件没有更新,要如何才能避免这种严重的错误呢?那就是必须进行异动交易的控制,在一个异动交易控制中,只要有一笔交易失败,那么之前所有交易的资料,将被还原至交易之前。
在Delphi 的Connection 对象已经提供了良好的控制程序。以TAdoConnection为例,它的语法格式如下:
try
// 开始进行交易控制
adoconnection1.BeginTrans;
// 写入您的交易程序
.......
.......
.......
// 将交易资料实际写入数据库
adoconnection1.CommitTrans;
except
// 如果发生意外,将之前所有已交易资料还原
adoconnection1.RollbackTrans;
end;
从上面的控制结构来看,它是将一个交易程序带入交易控制之中,那么也就是说,如果你原先写的交易程序如果没有做这样的控制,只要加入这样的控制程序,就可以进行异动交易。但在程序重整的技术中,有一点必须要注意,那就是尽量不要去更动现有的程序,因为一旦更动程序,就必须重新作测试,因此在不更动现有的程序的前提下,我们要如何去修改程序,让程序具有异动交易控制能力呢?
【面临的问题】
虽然Delphi 已经有了很好的解决方式,但是对一些已经写好的交易程序,我们在不变动原有的程序代码,如何进行功能的扩充呢?
【使用技术】
这个案例,我们将利用到重整技术的概念及样式设计来解决这个问题。范例中我们会用到Template Method 这个样式来设计一个程序,什么叫Template Method ?它的定义如下:
在父类别中定义一个算法的骨架,但将一些步骤延迟到其子类别中执行。套句话说,一个人犯罪是要坐牢的,这个犯罪要坐牢就是一个样版,至于做几年的牢,要看当时犯了什么罪才来决定。
这样的说法还是很抽象,我们进一步说明这个抽象的概念的运用时机:
利用Template Method 可以定义一些共享的算法,差异性部分则由子类别去实作,让一些有差异性的子类别,拥有共同的算法,如此则程序不但有弹性,且易于维护。
我们应该可以想象,异动交易控制就是一个算法,这个算法会被利用在许多需要控制异动交易的地方,每个异动交易的内容是不一样的,但异动控制的运算却是一致的。Template Method 这个样式的重点乃在区分变与不变的地方,将不变的部分写成骨架,将变的部分保留起来,对于一个异动交易控制程序来说,异动交易内容是变动的部分,而异动控的运算是不变的部分。
【实作范例】
在这个的范例中,我们并没有使用类别的继承层级,但却是运用了另一种方式去实现这个差异性的部分,只要是考虑与现有程序的协调问题,至于使用类别层级的方式,在结论中亦有说明其做法。
首先,在TAdoconnect所在的控制模块中,增加一个程序,这个程序跟上述的异动控制结构差不多,只加入了一个TrandProc的事件,在这个事件中,我们将允许执行一些差异的运算,因此很简单的,我们已经将Template Method 这个样式运用在程序设计中了。
function TDmConNection.RunTrans( TransProc:TNotifyEvent):boolean;
begin
try
adoconnection1.BeginTrans;
transProc(self);
adoconnection1.CommitTrans;
result:=true;
except
adoconnection1.RollbackTrans;
result:=false;
end;
end;
接下来,我们来看一下,如何运用这个样式来解决问提呢,假设你有一个异动程序是这样:
// 异动程序
procedure TForm1.rename(sender: TObject);
var
n:integer;
begin
adodataset1.First;
for n:=0 to adodataset1.RecordCount-1 do begin
if adodataset1[‘使用者‘]=‘bruce‘ then begin
adodataset1.edit;
adodataset1[‘异动‘]:=checkbox1.checked;
adodataset1.post;
end;
adodataset1.next;
end;
end;
现在,我们要为这个异动程序增加异动控能力,只要增加这样的一个程序就可以了:
procedure TForm1.Button1Click(Sender: TObject);
begin
if not dmconnection.RunTrans( rename )then
application.MessageBox(‘执行失败‘,‘错误‘,0);
end;
把现有的异动程序当成一个参数,传至RunTrans 这个异动交易控制的运算中,就大功告成。
【结        论】
这个范例运用Template Method 样式,避免修改已经完成的程序,同时又可以增加系统的功能。当然这个范例,也可以运用类别继承的方式来实现,其写作的方式,与上述的范例,亦相去无几,原事件改成宣告一个虚拟的程序,并在子类别中去实作这个程序即可,运用的概念是一样的,这正是所谓戏法人人会变,巧妙各有不同,融会贯通之后就可以运用自如了。
范例三
运用Factory 工厂模式设计多人使用的权限控制
作者:陈国生
Email:bruce123@edirect168.com
【解决问题】
在一个多人使用的系统中,根据不同的使用者,必须设计出不同的使用权限,要设计这样的权限控管机制,很显然相当复杂而且繁琐。因此,如何使用一个简单又容易维护的方式进行系统设计?在软件设计领域里面,简单就是美,这是在灵活塑模里面的一个宣言,追求简单,其实不简单,同样的一个问题,有千百种解法,如何使用一个简单又有弹性的设计,是程序设计者追求的极致,本范例的重点就是要具体实践这个目标。
【面临问题】
对已经写好的程序,我们不希望修改,因此如何以扩充功能的方法,将此权限控制的功能加入系统?
【运用技术】
在重整技术中,有一个重要的原则,就是不要去更动已经设计好的程序,因此我们将设计一个即插即用的单元,为系统扩充多人使用的控管机制,因此在这个构想下,我们运用Factory 工厂模式的设计样式来实现这个设计。
什么是Factory 工厂模式?工厂模式可以依实际需要,适时的产生我们所需要的对象,在于无法确定使用何种对象的时候非常好用。在Delphi 中已经内建许多不同类型的对象,我们可以随手取用,所以设计一个工厂模式可以说非常的简单。对象工厂本身有许多变形,如工厂方法( Factory Method ),抽象工厂( Abstract Factory ),雏形( Prototype ) 等等... 皆是。
现在,我们假设每一个使用者登入时,会对应到一个权限对象,然后针对这个对象去设计使用权限即可。换句话说,也就是说让不同的使用者对应到不同的对象上面,再运用工厂模式,根据不同的使用者,自动产生不同的对象,如此便可达到权限控制的目的。
当然,控制使用的权限,依控管方式不同,会方法有很多种,下面范例将提供其中一种控管方法。
【范例实作】
第一步:使用一个TDataModule 来存放一些TMainMenu对象。
第二步:TMainMenu 的数量根据使用者的多寡而定。
第三步:增加一个TActionList对象,将所有可执行程序统一交由TActionList 控管。
第四步:根据不同的使用权限,将TActionList 里面的TAction 指定至各个不同的TMainMenu 里面的Action 属性。
以上四个步骤,在Delphi 里面都可以可视化的完成,除了TAction里面的OnExecute必须动手写入欲执行的功能之外。
至此,我们已经把准备对应到使用者的权限对象TMainMenu 都设计完成了,接下来只要根据不同的使用者输出不同对象即可,这部分要如何设计呢?首先我在TDataNodule 的OnCreate事件中,加入判断使用者的功能,并输出对象:
procedure TDataModule1.DataModuleCreate(Sender: TObject);
var
MyMainMenu:TMainMenu;
User:string;
begin
// 根据不同使用者,取得不同的TMainMenu对象,以下以此类推...
User:=ParamStr(1);
if User=‘001‘ then MyMainMenu:=MainMenu1;
if User=‘002‘ then MyMainMenu:=MainMenu2;
.....
.....
// 接下来输出对象至主画面
Application.MainForm.Menu:=MyMainMenu;
end;
以上完成之后,最后的一个步骤是把TDataModule 在项目中自动建构起来,由于设计上是自动输出对象至MainForm,所以TDataModule 的建构必须在MainForm 之后,这样就算大功告成了。注意:本范例无处理User 登录的功能,系使用传递参数的方法来判断使用者,这部分请自行参考变化。
【结        论】
工厂模式的对象产生方式,可以动态产生,也可以静态建立,Delphi 中有两个对象可以很方便的存放这些事先建好的对象,一个是TForm ,一个TDataModule,在本范例中,是利用TDatamodule 存放事先建立好的对象,让Client 端可以很方便的取得这些对象。
静态建立的好处,在于可以利用Delphi 的可视化设计环境去建构这些对象,因此运用工厂模式及善用Delphi 现有的对象来设计,可以减少许多的程序代码,增加系统执行的稳定度,程序代码写的愈多,出错的机会就愈大,相对的系统也不容易维护,这应该是简单就是美的真谛吧!