前面花了三章篇幅说了VCL下的永续存储,有朋友说,觉得还缺乏一个在VCL大框架下的总结。既然要站在框架这个绝度,那么VCL 的永续储存不是简单的 "保存 - 读取",而需要理解是一套精心编排的设计模式舞蹈。那些看似零散的类和方法,实则遵循着严谨的设计逻辑,就像老式钟表里精准咬合的齿轮。
一、 VCL 永续储存的设计模式
设计模式这东西,年轻时总觉得是理论家的噱头,直到在项目中踩了足够多的坑才明白 —— 它们是前辈们用血泪总结的 "避坑指南"。VCL 的永续储存机制里,藏着好几段精妙的设计模式 "独舞"。
1、Two-Way Sequential
第一次理解 Two-Way Sequential 模式,是在和公司专门负责维修线路的老电工聊天。他说:"好的电路必须能送电也能断电,还得知道现在是送电还是断电。" 这话让我想到 ——VCL 的流操作不就是这样吗?既能写入数据(送电),又能读取数据(断电),还通过Position
属性记录当前位置(知道状态)。
这种双向顺序模式就像两个人对话:你一句(写),我一句(读),必须按顺序来,还得记得刚才说到哪了。TReader 和 TWriter 正是这种模式的完美实现,它们一个负责说,一个负责听,配合默契。
// Two-Way Sequential模式的简单实现
procedure TwoWaySequentialDemo;
var
Stream: TMemoryStream;
Writer: TWriter;
Reader: TReader;
name: string;
age: Integer;
begin
Stream := TMemoryStream.Create;
Writer := TWriter.Create(Stream, 1024);
Reader := TReader.Create(Stream, 1024);
try
// 写入数据(发送消息)
Writer.WriteString('老程序员');
Writer.WriteInteger(45);
Writer.Flush;
// 重置位置(准备倾听)
Stream.Position := 0;
// 读取数据(接收消息)
name := Reader.ReadString;
age := Reader.ReadInteger;
ShowMessageFmt('姓名:%s,年龄:%d', [name, age]);
finally
Reader.Free;
Writer.Free;
Stream.Free;
end;
end;
2、Adapter
年轻时层做一个跨系统数据同步项目时,遇到个头疼问题:我们的设备用自定义协议输出数据,而合作方的系统只认标准 XML。最后了解到设计模式,是 Adapter 模式救了场 —— 就像旅行时带的万能电源转换器,让两种不兼容的接口能顺畅对接。
VCL 的永续储存里,Adapter 模式无处不在。当一个组件的存储格式和流系统不兼容时,就需要一个 "转换器" 来协调。Delphi 提供了多种 Adapter 实现方式,各有各的妙用。
接口 - 类别混合的 Adapter:像带多接口的转换器,既懂组件的 "语言",又懂流的 "规则"。
// 接口-类别混合的Adapter示例
type
// 组件的接口(设备插头)
IMyComponent = interface
['{GUID}']
function GetData: string;
procedure SetData(const Value: string);
end;
// 流的接口(电源插座)
IStreamAdapter = interface
['{GUID}']
procedure SaveToStream(Stream: TStream);
procedure LoadFromStream(Stream: TStream);
end;
// Adapter(转换器)
TComponentStreamAdapter = class(TInterfacedObject, IStreamAdapter)
private
FComponent: IMyComponent;
public
constructor Create(AComponent: IMyComponent);
procedure SaveToStream(Stream: TStream);
procedure LoadFromStream(Stream: TStream);
end;
constructor TComponentStreamAdapter.Create(AComponent: IMyComponent);
begin
inherited Create;
FComponent := AComponent;
end;
procedure TComponentStreamAdapter.SaveToStream(Stream: TStream);
var
Writer: TWriter;
begin
Writer := TWriter.Create(Stream, 1024);
try
// 把组件数据转换成流能理解的格式
Writer.WriteString(FComponent.GetData);
finally
Writer.Free;
end;
end;
procedure TComponentStreamAdapter.LoadFromStream(Stream: TStream);
var
Reader: TReader;
begin
Reader := TReader.Create(Stream, 1024);
try
// 把流数据转换成组件能理解的格式
FComponent.SetData(Reader.ReadString);
finally
Reader.Free;
end;
end;
类别继承的 Adapter:更像定制化的转换器,通过继承获得基础能力,再通过重写适配特定需求。比如 TComponent 就是通过继承 TPersistent,获得了基础的存储能力,再通过重写方法适配组件特有的存储需求。
// 类别继承的Adapter示例
type
// 基础持久化类(标准插座)
TBasePersistence = class(TPersistent)
public
procedure Save(Stream: TStream); virtual;
procedure Load(Stream: TStream); virtual;
end;
// 自定义组件(特殊插头)
TMySpecialComponent = class(TBasePersistence)
private
FSpecialData: Integer;
public
property SpecialData: Integer read FSpecialData write FSpecialData;
// 重写方法实现适配
procedure Save(Stream: TStream); override;
procedure Load(Stream: TStream); override;
end;
procedure TBasePersistence.Save(Stream: TStream);
begin
// 基础存储逻辑
end;
procedure TBasePersistence.Load(Stream: TStream);
begin
// 基础加载逻辑
end;
procedure TMySpecialComponent.Save(Stream: TStream);
var
Writer: TWriter;
begin
inherited; // 先执行基础逻辑
Writer := TWriter.Create(Stream, 1024);
try
// 适配特殊数据的存储
Writer.WriteInteger(FSpecialData);
finally
Writer.Free;
end;
end;
procedure TMySpecialComponent.Load(Stream: TStream);
var
Reader: TReader;
begin
inherited; // 先执行基础逻辑
Reader := TReader.Create(Stream, 1024);
try
// 适配特殊数据的加载
FSpecialData := Reader.ReadInteger;
finally
Reader.Free;
end;
end;
3、设计模式的合奏
记得有个项目需要保存用户绘制的工艺流程图 —— 里面有各种形状、线条、文字标注,每种元素都有自己的属性。我们用 Two-Way Sequential 模式保证读写顺序,用 Adapter 模式让每种图形元素都能和流系统对话,最终实现了 "绘制 - 保存 - 再编辑" 的完整闭环。
这些模式单独看可能平平无奇,但组合起来就像一支精密的乐队,每个乐器(模式)都在恰当的位置发挥作用,共同奏响了 "永续储存" 这首曲子。
二、串行流类别
如果说设计模式是流操作的 "章法",那串行流类别就是承载数据的 "容器"。Delphi 提供了一系列流类,就像生活中不同的容器 —— 有的适合临时存放(如内存流),有的适合长期保存(如文件流),有的适合网络传输(如缓冲流)。
1、TStream
TStream 是所有流类的基类,就像所有容器都有的基本属性 —— 能装东西,能知道装了多少,能移动到不同位置取东西。它定义了Read
、Write
、Seek
等基础方法,不管哪种流,都得遵守这些基本规则。
2、TMemoryStream
做实时数据处理时,TMemoryStream 是我的最爱。它把数据存在内存里,读写速度飞快,就像把临时想法写在便签上,方便随时修改。但缺点也和便签一样 —— 程序关掉就没了。
// 用TMemoryStream处理临时数据
procedure MemoryStreamDemo;
var
MemStream: TMemoryStream;
Data: array[0..9] of Byte;
i: Integer;
ReadData: array[0..9] of Byte;
begin
MemStream := TMemoryStream.Create;
try
// 准备数据
for i := 0 to 9 do
Data[i] := i;
// 写入内存流
MemStream.Write(Data, SizeOf(Data));
// 读取数据
MemStream.Position := 0;
MemStream.Read(ReadData, SizeOf(ReadData));
// 数据处理...
finally
MemStream.Free; // 释放时数据就消失了
end;
end;
3、TFileStream
需要长期保存数据时,TFileStream 就派上用场了。它把数据存在硬盘上,就像把重要文件放进档案盒,关机也不会丢失。当年做配置保存功能,就是用它把用户设置稳稳当当地存在config.dat
里。
// 用TFileStream保存配置
procedure SaveConfig;
var
FileStream: TFileStream;
Config: string;
begin
Config := 'WindowPos=100,100,800,600'#13#10'FontSize=12';
// 创建或打开文件
if FileExists('config.dat') then
FileStream := TFileStream.Create('config.dat', fmOpenWrite)
else
FileStream := TFileStream.Create('config.dat', fmCreate);
try
// 写入配置
FileStream.WriteBuffer(Config[1], Length(Config));
finally
FileStream.Free;
end;
end;
4、其他实用流类
- TStringStream:专门处理字符串的 "文字袋",省去了字符串和字节数组的转换麻烦。
- TBufferedStream:带缓冲的 "快递箱",通过先缓存再批量读写,提高文件或网络操作效率。
- TCompressionStream/TDecompressionStream:会 "压缩" 的容器,像真空袋一样减少数据体积,适合传输和存储大文件。
当年做一个日志分析工具,需要处理几百 MB 的日志文件,正是用了 TBufferedStream,让读取速度提升了近十倍。这让我明白:选对合适的流类,就像用对了工具,能事半功倍。
最后小结:
回望这些流类和设计模式,忽然发现 VCL 的永续储存机制藏着一种朴素的智慧:它不追求极致的精巧,而是通过分层设计(基础流类→设计模式→组件实现),让复杂问题变得可控。就像老木匠做柜子,先做好标准的木板(流类),再设计好连接方式(模式),最后根据需求组合出不同的柜子(组件)。这种思想在今天的微服务、组件化开发中依然闪耀 —— 技术在变,但解决问题的底层逻辑,往往是相通的。是它们让我明白:好的技术不是炫技,而是像水一样,无形却能适应各种容器,默默解决问题。这或许就是 VCL 永续储存机制留给我们的,超越代码本身的启示。未完待续....