You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Delphi是否支持可变泛型?观察者模式优化方案问询

Delphi 通用观察者模式的优化方案

首先明确:Delphi 目前(包括最新的 11.x 版本)不支持 C++ 风格的可变参数泛型(Variadic Templates),无法通过可变参数泛型直接实现适配任意回调签名的类型安全观察者/主题类。针对你遇到的「新增回调签名需同步扩展TSubject和TObserver」的繁琐问题,以下是几种可行的替代方案:

方案1:方法引用+泛型重载(类型安全,推荐)

利用 Delphi 的泛型方法引用,针对不同的回调签名重载TSubject的注册、通知方法,无需单独定义多个TObserver类。这种方式保持编译期类型安全,新增签名时仅需添加对应的重载方法即可。

示例代码:

type
  // 定义不同签名的泛型方法引用
  TNotifyProc<T> = reference to procedure(const Value: T);
  TNotifyProc<T1, T2> = reference to procedure(const A: T1; const B: T2);
  TNotifyProc<T1, T2, T3> = reference to procedure(const A: T1; const B: T2; const C: T3);

  TSubject = class
  private
    FObservers1: array of TNotifyProc<Integer>;
    FObservers2: array of TNotifyProc<String, Boolean>;
    FObservers3: array of TNotifyProc<Double, TDateTime, String>;
  public
    // 重载注册方法
    procedure AddObserver(const Proc: TNotifyProc<Integer>);
    procedure AddObserver(const Proc: TNotifyProc<String, Boolean>);
    procedure AddObserver(const Proc: TNotifyProc<Double, TDateTime, String>);

    // 重载通知方法
    procedure Notify(const Value: Integer);
    procedure Notify(const Msg: String; const Flag: Boolean);
    procedure Notify(const Num: Double; const Time: TDateTime; const Desc: String);
  end;

// TSubject 实现
procedure TSubject.AddObserver(const Proc: TNotifyProc<Integer>);
begin
  SetLength(FObservers1, Length(FObservers1)+1);
  FObservers1[High(FObservers1)] := Proc;
end;

procedure TSubject.Notify(const Value: Integer);
var
  Proc: TNotifyProc<Integer>;
begin
  for Proc in FObservers1 do
    Proc(Value);
end;

// 其他重载方法实现类似,此处省略

方案2:RTTI+TValue 可变参数(高灵活性,牺牲部分类型安全)

如果需要支持任意数量/类型的回调参数,可以利用 Delphi 的TValue(位于System.Rtti单元)传递可变参数,观察者通过解析TValue数组获取参数。这种方式无需修改TSubject即可支持新的回调签名,但编译期无法检查参数类型和数量,需自行处理参数校验。

示例代码:

uses
  System.Rtti;

type
  TGenericNotifyProc = reference to procedure(const Params: array of TValue);

  TSubject = class
  private
    FObservers: array of TGenericNotifyProc;
  public
    procedure AddObserver(const Proc: TGenericNotifyProc);
    procedure Notify(const Params: array of TValue);
  end;

// TSubject 实现
procedure TSubject.AddObserver(const Proc: TGenericNotifyProc);
begin
  SetLength(FObservers, Length(FObservers)+1);
  FObservers[High(FObservers)] := Proc;
end;

procedure TSubject.Notify(const Params: array of TValue);
var
  Proc: TGenericNotifyProc;
begin
  for Proc in FObservers do
    Proc(Params);
end;

// 使用示例
var
  Subject: TSubject;
begin
  Subject := TSubject.Create;
  // 注册一个接收字符串+布尔值的观察者
  Subject.AddObserver(procedure(const Params: array of TValue)
    var
      Msg: String;
      Flag: Boolean;
    begin
      if (Length(Params) >= 2) and Params[0].IsType<String> and Params[1].IsType<Boolean> then
      begin
        Msg := Params[0].AsString;
        Flag := Params[1].AsBoolean;
        // 业务逻辑处理
      end;
    end);
  // 发送通知
  Subject.Notify(['Operation completed', True]);
end;

方案3:接口适配器+泛型(解耦型,兼顾安全与灵活)

定义一个基础的IObserver接口,然后针对不同的回调签名实现泛型适配器类,将方法引用适配为接口。这种方式下,TSubject仅需处理IObserver接口,新增回调签名时只需添加对应的适配器类,无需修改TSubject代码。

示例代码:

uses
  System.Rtti;

type
  IObserver = interface
    ['{E2F56A55-7E4A-4D3B-8F1C-5D7B8F3A1E2D}']
    procedure Notify(const Params: array of TValue);
  end;

  // 适配单参数回调的泛型适配器
  TObserverAdapter<T> = class(TInterfacedObject, IObserver)
  private
    FProc: reference to procedure(const Value: T);
  public
    constructor Create(const Proc: reference to procedure(const Value: T));
    procedure Notify(const Params: array of TValue);
  end;

  // 适配双参数回调的泛型适配器
  TObserverAdapter<T1, T2> = class(TInterfacedObject, IObserver)
  private
    FProc: reference to procedure(const A: T1; const B: T2);
  public
    constructor Create(const Proc: reference to procedure(const A: T1; const B: T2));
    procedure Notify(const Params: array of TValue);
  end;

  TSubject = class
  private
    FObservers: array of IObserver;
  public
    procedure AddObserver(const Observer: IObserver);
    procedure Notify(const Params: array of TValue);
  end;

// TObserverAdapter<T> 实现
constructor TObserverAdapter<T>.Create(const Proc: reference to procedure(const Value: T));
begin
  inherited Create;
  FProc := Proc;
end;

procedure TObserverAdapter<T>.Notify(const Params: array of TValue);
begin
  if (Length(Params) >= 1) and Params[0].IsType<T> then
    FProc(Params[0].AsType<T>);
end;

// TObserverAdapter<T1,T2> 实现类似
constructor TObserverAdapter<T1, T2>.Create(const Proc: reference to procedure(const A: T1; const B: T2));
begin
  inherited Create;
  FProc := Proc;
end;

procedure TObserverAdapter<T1, T2>.Notify(const Params: array of TValue);
begin
  if (Length(Params) >= 2) and Params[0].IsType<T1> and Params[1].IsType<T2> then
    FProc(Params[0].AsType<T1>, Params[1].AsType<T2>);
end;

// TSubject 实现
procedure TSubject.AddObserver(const Observer: IObserver);
begin
  SetLength(FObservers, Length(FObservers)+1);
  FObservers[High(FObservers)] := Observer;
end;

procedure TSubject.Notify(const Params: array of TValue);
var
  Observer: IObserver;
begin
  for Observer in FObservers do
    Observer.Notify(Params);
end;

// 使用示例
var
  Subject: TSubject;
begin
  Subject := TSubject.Create;
  // 注册单参数观察者
  Subject.AddObserver(TObserverAdapter<Integer>.Create(procedure(const Value: Integer)
    begin
      // 处理整数参数
    end));
  // 注册双参数观察者
  Subject.AddObserver(TObserverAdapter<String, Boolean>.Create(procedure(const Msg: String; const Flag: Boolean)
    begin
      // 处理字符串+布尔参数
    end));
end;

方案选择建议

  • 若需严格编译期类型安全,优先选择方案1,实现简单且易于维护;
  • 若需支持任意参数的回调,且能接受运行时参数校验,选择方案2;
  • 若希望主题类与观察者签名完全解耦,新增签名时无需修改主题类,选择方案3。

内容的提问来源于stack exchange,提问作者Adrian Maire

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 17:57:41