技术演进中的开发沉思-67 DELPHI VCL系列:VCL 中的设计模式之美

今天是周末,在我当程序员的日子周末似乎也是要加班的。随着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 的设计哲学:让复杂的事情变简单,让简单的事情变优雅。技术在变,语言在变,但解决问题的智慧,永远值得我们细细品味。未完待续...........

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值