设计模式在制作升级文件包中的应用
钱波 钱晓贤 高春玲
引言
软件系统的复杂性越来越大,而现有的类库也已经不能满足工程的需要,软件开发小组得到的建议是除了软件本身的特性突出,快速投放市场,成本低以外,还有性能和安全,并且软件能够做到改动容易,能及时交付,升级快速。软件构架的确会成为首要目标,但是架构可能很难说清楚是怎么回事,因为事实并不会如设计师所愿,设计展开以后,需求和构架解决方案将会越来越具体,设计师该怎样做呢?
设计模式的提出
模式在构造复杂系统得重要性已经在其它领域被认可,软件领域中的设计模式为开发人员提供了一种使用专家设计经验的有效途径。灵活使用各种设计模式看来便成了一种设计上的技巧,已有的设计模式确实提供了设计的基础,但是设计模式并不是限于成规,关键在于怎样灵活的运用。
实例升级文件的制作
我们的文件是所需信息和几个文件的合并,后来在需要的基础上进行压缩,当然,升级文件下载后正好使个相反的过程,解压缩后分拆文件,然后把它们放置到合适的目录下去,实际上这个过程也可以看作是备份文件的产生和备份恢复。使用继承是添加功能的有效途径,我们可以在以前的类上派生一个类添加解压缩的功能,在完成以后就可以使用新增的功能了,这的确是一个方法,但是还有更为灵活的方法,更容易理解和分拆功能。我们可以利用模式装饰者,模式装饰者是一种对象结构型模式,就增加功能来说,使用继承是添加功能的一种有效途径,但是Decorator 模式比生成子类更为灵活。
现在首先分拆功能,可能是这样的:
图一 功能拆分类图
从图一上看,TFileInfo 类实际上是我们的接口,是位于顶层的类,现在主要需要的是三个继承者,1合并文件类TFileInfoReadIn,2分拆文件的类,TFileInfoReadOut,3解压缩类TZipFile,在开始的时候并没有构造TFileInfo这样一个顶层类,。因为看起来并不需要他,我们可以用单独的TFileInfoReadIn 对象来构建合并的程序,完成后交给TZipFile的去压缩,在反过来的过程中用TZipFile对象来解压文件,用TFileInfoReadOut来分解文件到目录中去。功能已经比较单一了。
如图一所示,现在试着用一个抽象的基类来同时操纵三个可能毫不相关的类,它们的接口函数并不一定统一,强行定义统一的接口可能不太好理解,但是在顶层类还是这样定义了:
procedure OpBaseList(var ls:TStrings);virtual;abstract; //操纵信息字段
procedure OpFileList;virtual;abstract; //操纵所有文件
OpBaseList函数是操纵文件前所加的其他信息字段,OpFileList将操纵所有的文件,因为是纯抽象的函数,即不用去实现它们,(另外需要说明的是,顶层类根据可能还需要扩充更多的接口函数)。在顶层类里维护了一个文件信息列表:FileList:TStringList,包含了所有文件的路径列表。(所有类只列出接口,详细请看源码)
Type TFileInfo=Class(TPersistent)
public
Procedure OpBaseList(var ls:TStrings);virtual;abstract; //操纵信息字段
Procedure OpFileList;virtual;abstract; //操纵所有文件
end;
关键的一步在于建立装饰类:
Type TFileDecorator=Class(TFileInfo)
public
Constructor Create(FileInfo:TFileInfo);
procedure OpBaseList(var ls:TStrings);override;
procedure OpFileList;override;
end;
也许装饰类看以来比较怪异,他维护了一个TFileInfo的私有对象,事实上它是连接其他从TFileInfo类继承的对象的连接者,m_FileInfo对象引用了构造函数传进的对象FileInfo.
Constructor TFileDecorator.Create(FileInfo: TFileInfo);
begin
m_FileInfo:=FileInfo;
end;
在OpBaseList和OpFileList接口中看起来更为简单:
procedure TFileDecorator.OpBaseList(var ls: TStrings);
begin
// inherited;
if m_FileInfo<>nil then
m_FileInfo.OpBaseList(ls);
end;
procedure TFileDecorator.OpFileList;
begin
// inherited;
if m_FileInfo<> nil then
m_FileInfo.OpFileList;
end;
看起来构造器中传进来的对象开始发生作用。好,接下来的工作是实现三个功能类,实际的代码将放在功能类中:
type TFileInfoReadIn=class(TFileDecorator)
protected
procedure WriteAFileIn(FilePathName:String); //写入一个文件
public
constructor Create(FileInfo:TFileInfo;FileName:String); //
procedure OpBaseList(var ls:TStrings);override; //操纵信息列表
procedure OpFileList;override; //操纵所有文件
end;
type TFileInfoReadOut=class(TFileDecorator)
public
constructor Create(FileInfo:TFileInfo;FileName:String);
procedure OpBaseList(var ls:TStrings);override;
procedure OpFileList;override;
end;
现在看实现类TFileInfoReadIn.OpFileList函数,怎样操纵文件:
procedure TFileInfoReadIn.OpFileList;
var I:Integer;
begin
inherited; //继承功能
m_Writer.WriteInteger(m_FileList.Count);//写入文件的个数
for I:=0 to m_FileList.Count-1 do begin
WriteAFileIn(m_FileList.Strings[I]); //根据文件全路径写入文件
end;
if Assigned(m_Writer) then
FreeAndNil(m_Writer);
if Assigned(m_FileStream) then
FreeAndNil(m_FileStream);
end;
WriteAFileIn函数实现:
procedure TFileInfoReadIn.WriteAFileIn(FilePathName: String);
var ReadInt:Integer;
FSource:TFileStream;
begin
FSource:=TFileStream.Create(FilePathName,fmOpenRead); //这里重新创建文件输入流
m_Writer.WriteInteger(FSource.Size); //写入文件的长度
m_Writer.WriteString(ExtractFileName(FilePathName)); //写入文件名
repeat
ReadInt:=FSource.Read(m_buffer^,BUFFER_SIZE); //源文件内容读入缓冲
m_Writer.Write(m_Buffer^,ReadInt);
until ReadInt<BUFFER_SIZE;//文件的内容 //重复读取写入文件过程
FreeAndNil(FSource); //释放文件流
end;
至于TFileInfoReadOut类的OpFileList实现函数,和TFileInfoReadIn过程相反,这里就不列出了,具体看源代码。
关键还在于第三个类TZipFile,这个类,实现了文件的压缩和解压,首先单元里加上ZLib单元,这样我们可以利用TCompressStream类来实现文件流的压缩和解压,这里有一个问题,TZipFile类利是其他的程序员交付过来的,接口并不统一,但是适配器模式可以让我们很好地解决这类问题。
图二 为装饰类加入适配器
图二所示,TZipFile包含了CompressFile()和DeCompressFile()函数,而接口OpBaseList和OpFileList不变,实际上TZipFile可以被称为适配器。
type TZipFile=class(TFileDecorator)
private
m_ZipFileName:String;
public
Constructor Create(FileInfo:TFileInfo;FileName:String='');
Destructor Destroy;override;
function CompressFile(const SrcFile, DesFile: string;……. ): TCompressResult;
//压缩文件
function DeCompressFile(const SrcFile, DesFile: string;………):TCompressResult;
//解压文件
procedure OpBaseList(var ls:TStrings);override; //接口依然是这两个,但这个接口什么////都不做
procedure OpFileList;override; // 调用压缩和解压
end;
解释完模式后看看我们怎么运用我们的模式,如图三是制作的这个小工具的界面:
图三 工具界面
图三是我们的界面,添加完文件后触发打包按钮,Exe文件目录下会出现以文件名称Edit框(Edt_Edition)为文件名的合并文件Edt_Edition.dat文件和压缩文件Edt_Edttion.pag。在windows2000和Delphi7下调试成功,解包过程相反。
点击添加文件后,文件显示列表里会加上所需要的文件,现在的门面功能即是将TEdit盒子里的信息内容写入目标文件A,然后将列表中的文件合并到文件A里面,最后进行压缩。
现在可以定义一个aRead 和aZip 对象
var aREAD:TFileInfoReadIn; aZip:TZipFile;
begin
aRead:=TFileInfoReadIn.Create(nil,’FileName’);
////////………
aZip:=TZipFile.Create(aRead); //aZip对象添加aRead对象
OperatorFile(aZip); //操纵aZip对象
aRead.Free;
aZip.Free;
end;
操作文件很简单,调用两个接口函数
procedure TMainFrm.OperatorFile(Obj: TFileInfo);
begin
obj.OpBaseList(F_Strings);
obj.OpFileList; //如果还有其他接口,可以在这里逐个调用
end;
如果根据需要不用压缩文件,很简单,aZip:=TZipFile.Create(aRead)这句去掉即可。又如果我们写出了新的算法去压缩,可以修改TZipFile,也可能的调用方式就是一句aNewZip:=TNewZipFile.Create(aRead);你还可以以这种方式:
TFileInfoObj:=TFileInfoA.Create(TFileInfoB.Create(TFileInfoC.Create(…)));
然后调用接口TFileInfoObj.OpFileList; 一句话将所有对象的接口一次全都调用。最重要的是功能可以拆分:例如
TFileInfoObj:=TFileInfoB.Create(TFileInfoC.Create));
TFileInfoObj:=TFileInfoA.Create(nil);
TFileInfoObj:=TFileInfoB.Create(TFileInfoA.Create(nil));
////////
这样功能变成可设计的,就像是搭积木,在接口不合适的地方不要忘了适配器等结构型模式。
小结
事实上,考查一下Delphi的TStream和TMemoryStream,TFileStream,TCompressStream 等本身就是一个极好的装饰样式的实例。