今天是周末,在我当程序员的日子周末似乎也是要加班的。随着VCL的进一步梳理,今天我们还是说说在VCL设计模式之美。Delphi VCL 早已用设计模式搭建了隐形的协作框架,就像老北京四合院的榫卯结构,看不到钉子却坚固无比。

一、 Framework 让代码各安其位
设计模式这东西,初学时总觉得像武侠小说里的招式图谱,看得懂却用不活。直到在项目中反复碰壁才明白,那些看似抽象的模式,其实是解决 "谁该做什么"、"模块间如何对话" 的朴素智慧。VCL 框架把这些智慧融入了骨血,形成了一套自给自足的生态系统。
1、Notify 设计样例
小区物业要停水,不需要挨家挨户敲门通知,只需在公告栏贴一张通知 —— 这就是 Notify 模式的精髓:事件发生者无需知道谁会响应,只需广播消息;响应者只需关注自己关心的消息。
VCL 中最典型的 Notify 实现就是事件机制。比如按钮点击时,它根本不用知道有多少段代码在等着处理这个点击,只需触发 OnClick 事件即可。这种 "发布 - 订阅" 模式,让系统各部分解耦得像独立的乐高积木。
// Notify模式的简化实现
type
// 通知接口(定义公告栏)
INotifier = interface
['{GUID}']
procedure RegisterObserver(Observer: IObserver);
procedure UnregisterObserver(Observer: IObserver);
procedure NotifyObservers(Message: string);
end;
// 观察者接口(定义住户)
IObserver = interface
['{GUID}']
procedure OnNotify(Message: string);
end;
// 具体通知者(物业)
TDataManager = class(TInterfacedObject, INotifier)
private
FObservers: array of IObserver;
public
procedure RegisterObserver(Observer: IObserver);
procedure UnregisterObserver(Observer: IObserver);
procedure NotifyObservers(Message: string);
procedure UpdateData; // 数据更新时触发通知
end;
// 具体观察者(住户A:日志系统)
TLogger = class(TInterfacedObject, IObserver)
procedure OnNotify(Message: string);
end;
// 具体观察者(住户B:状态栏)
TStatusBar = class(TInterfacedObject, IObserver)
procedure OnNotify(Message: string);
end;
// 物业实现通知功能
procedure TDataManager.RegisterObserver(Observer: IObserver);
begin
SetLength(FObservers, Length(FObservers)+1);
FObservers[High(FObservers)] := Observer;
end;
procedure TDataManager.UnregisterObserver(Observer: IObserver);
var
i: Integer;
begin
for i := 0 to High(FObservers) do
if FObservers[i] = Observer then
begin
// 从数组中移除观察者(简化实现)
FObservers[i] := FObservers[High(FObservers)];
SetLength(FObservers, Length(FObservers)-1);
Break;
end;
end;
procedure TDataManager.NotifyObservers(Message: string);
var
i: Integer;
begin
// 广播消息给所有观察者
for i := 0 to High(FObservers) do
FObservers[i].OnNotify(Message);
end;
procedure TDataManager.UpdateData;
begin
// 数据更新后通知所有观察者
NotifyObservers('数据已更新');
end;
// 日志系统收到通知
procedure TLogger.OnNotify(Message: string);
begin
Writeln('日志记录:' + Message);
end;
// 状态栏收到通知
procedure TStatusBar.OnNotify(Message: string);
begin
Writeln('状态栏显示:' + Message);
end;
// 使用示例
procedure DemoNotifyPattern;
var
DataManager: TDataManager;
Logger: TLogger;
StatusBar: TStatusBar;
begin
DataManager := TDataManager.Create;
Logger := TLogger.Create;
StatusBar := TStatusBar.Create;
// 注册观察者
DataManager.RegisterObserver(Logger);
DataManager.RegisterObserver(StatusBar);
// 触发数据更新,自动通知所有观察者
DataManager.UpdateData;
// 清理
DataManager.UnregisterObserver(Logger);
DataManager.UnregisterObserver(StatusBar);
StatusBar.Free;
Logger.Free;
DataManager.Free;
end;
当年做库存管理系统时,这个模式帮了大忙。当库存低于阈值时,系统要同时触发补货提醒、暂停销售、记录预警日志 —— 用 Notify 模式实现后,新增一个通知对象(比如发送邮件给采购)只需加个观察者,完全不用修改核心的库存检查代码。这种 "开 - 闭原则" 的实践,让系统像树一样可以逐年添枝加叶,而不必把树干推倒重来。
2、 Facade 设计样例:
Facade 模式的核心理念:为复杂系统提供一个简洁入口,隐藏内部细节。
VCL 中的 TForm 就是个经典 Facade。我们调用 Form.Show () 时,根本不用关心 Windows 消息循环、窗口创建、控件绘制这些底层细节,Form 已经把一切都打理好了。就像去酒店找前台,不用知道客房部、餐饮部怎么运作,前台会帮你协调一切。
// Facade模式的简化实现
type
// 子系统1:数据库(厨房)
TDatabase = class
public
procedure Connect;
procedure Query(SQL: string);
procedure Disconnect;
end;
// 子系统2:加密模块(安保)
TEncryptor = class
public
function Encrypt(Data: string): string;
function Decrypt(Data: string): string;
end;
// 子系统3:报表引擎(服务台)
TReportEngine = class
public
procedure Generate(Content: string);
procedure Print;
end;
// Facade:业务服务(前台)
TReportService = class
private
FDB: TDatabase;
FEncryptor: TEncryptor;
FReport: TReportEngine;
public
constructor Create;
destructor Destroy; override;
procedure GenerateSecureReport(SQL: string); // 简化接口
end;
// 实现子系统
procedure TDatabase.Connect; begin Writeln('数据库连接成功'); end;
procedure TDatabase.Query(SQL: string); begin Writeln('执行查询:' + SQL); end;
procedure TDatabase.Disconnect; begin Writeln('数据库断开连接'); end;
function TEncryptor.Encrypt(Data: string): string; begin Result := '加密[' + Data + ']'; end;
function TEncryptor.Decrypt(Data: string): string; begin Result := '解密[' + Data + ']'; end;
procedure TReportEngine.Generate(Content: string); begin Writeln('生成报表:' + Content); end;
procedure TReportEngine.Print; begin Writeln('打印报表'); end;
// 实现Facade
constructor TReportService.Create;
begin
inherited;
FDB := TDatabase.Create;
FEncryptor := TEncryptor.Create;
FReport := TReportEngine.Create;
end;
destructor TReportService.Destroy;
begin
FReport.Free;
FEncryptor.Free;
FDB.Free;
inherited;
end;
procedure TReportService.GenerateSecureReport(SQL: string);
begin
// 隐藏复杂流程,提供简单接口
FDB.Connect;
FDB.Query(SQL);
FDB.Disconnect;
FReport.Generate(FEncryptor.Encrypt('报表内容'));
FReport.Print;
end;
// 使用示例
procedure DemoFacadePattern;
var
ReportService: TReportService;
begin
ReportService := TReportService.Create;
try
// 使用者只需调用一个方法,无需关心内部细节
ReportService.GenerateSecureReport('SELECT * FROM sales');
finally
ReportService.Free;
end;
end;
曾重构一个遗留系统时,我们用 Facade 模式把 17 个相互依赖的模块包装成 3 个简洁接口,不仅让新接手的程序员快速上手,更意外地减少了 60% 的跨模块调用错误。这让我想起老木匠的话:"好家具的卯榫都藏在里面,表面要干净利落。"
3、 Command 设计样例 / Action 设计样例
Command 模式:把请求封装成对象,就像把点菜单变成实体令牌。
VCL 的 TAction 是 Command 模式的精彩实现。一个 "保存" 动作,可以同时绑定给菜单、工具栏按钮、快捷键,执行时调用同一个处理过程,状态变化时所有绑定的控件自动同步。就像一张点餐单可以复制多份,传给后厨、收银台、服务员,信息始终保持一致。
// Command/Action模式的简化实现
type
// 命令接口(菜单)
ICommand = interface
['{GUID}']
procedure Execute;
function GetEnabled: Boolean;
procedure SetEnabled(Value: Boolean);
property Enabled: Boolean read GetEnabled write SetEnabled;
end;
// 具体命令:保存(阳春面)
TSaveCommand = class(TInterfacedObject, ICommand)
private
FEnabled: Boolean;
FFileName: string;
public
constructor Create(AFileName: string);
procedure Execute;
function GetEnabled: Boolean;
procedure SetEnabled(Value: Boolean);
end;
// 具体命令:撤销(退换菜)
TUndoCommand = class(TInterfacedObject, ICommand)
private
FEnabled: Boolean;
public
procedure Execute;
function GetEnabled: Boolean;
procedure SetEnabled(Value: Boolean);
end;
// 命令调用者:按钮(服务员)
TCommandButton = class
private
FCommand: ICommand;
public
constructor Create(ACmd: ICommand);
procedure Click; // 触发命令
property Caption: string;
end;
// 实现保存命令
constructor TSaveCommand.Create(AFileName: string);
begin
inherited Create;
FFileName := AFileName;
FEnabled := True;
end;
procedure TSaveCommand.Execute;
begin
Writeln('保存文件:' + FFileName);
end;
function TSaveCommand.GetEnabled: Boolean; begin Result := FEnabled; end;
procedure TSaveCommand.SetEnabled(Value: Boolean); begin FEnabled := Value; end;
// 实现撤销命令
procedure TUndoCommand.Execute; begin Writeln('执行撤销操作'); end;
function TUndoCommand.GetEnabled: Boolean; begin Result := FEnabled; end;
procedure TUndoCommand.SetEnabled(Value: Boolean); begin FEnabled := Value; end;
// 实现命令按钮
constructor TCommandButton.Create(ACmd: ICommand);
begin
inherited Create;
FCommand := ACmd;
end;
procedure TCommandButton.Click;
begin
if FCommand.Enabled then
FCommand.Execute;
end;
// 使用示例
procedure DemoCommandPattern;
var
SaveCmd: TSaveCommand;
UndoCmd: TUndoCommand;
btnSave1, btnSave2, btnUndo: TCommandButton;
begin
// 创建命令
SaveCmd := TSaveCommand.Create('document.txt');
UndoCmd := TUndoCommand.Create;
// 多个按钮绑定同一个命令
btnSave1 := TCommandButton.Create(SaveCmd);
btnSave1.Caption := '菜单保存';
btnSave2 := TCommandButton.Create(SaveCmd);
btnSave2.Caption := '工具栏保存';
btnUndo := TCommandButton.Create(UndoCmd);
btnUndo.Caption := '撤销';
// 执行命令
btnSave1.Click; // 保存文件:document.txt
btnSave2.Click; // 保存文件:document.txt
// 禁用命令后,所有绑定的按钮都受影响
SaveCmd.Enabled := False;
btnSave1.Click; // 不执行任何操作
// 清理
btnUndo.Free;
btnSave2.Free;
btnSave1.Free;
UndoCmd.Free;
SaveCmd.Free;
end;
做文本编辑器项目时,这个模式让我们轻松实现了 "撤销 / 重做" 功能。每个编辑操作都被封装成 Command 对象,存入历史列表,需要时取出执行反向操作即可。更妙的是权限控制 —— 当用户没有保存权限时,只需设置 SaveCommand.Enabled := False,所有关联的保存按钮会自动变灰,省去了逐个设置控件状态的麻烦。
二、设计模式的朴素本质
回望这些设计模式,忽然发现它们都在解决一个根本问题:如何让复杂系统保持秩序。Notify 模式解决 "谁该知道什么",Facade 模式解决 "如何简化接口",Command 模式解决 "如何标准化操作"。
就像城市规划,Notify 是通信网络,让各个部门能协同工作;Facade 是城市入口,让外来者轻松进入;Command 是交通规则,让各种行为有序进行。VCL 框架的强大,正在于它把这些 "城市规划理念" 悄无声息地融入了每个组件。
三、最后小结
年轻时总追求新奇的设计模式,现在才明白,最好的模式往往是最朴素的 —— 就像 Delphi 的设计哲学:让复杂的事情变简单,让简单的事情变优雅。技术在变,语言在变,但解决问题的智慧,永远值得我们细细品味。未完待续...........

5274

被折叠的 条评论
为什么被折叠?



