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
相关产品推荐
相关产品推荐

