首页 > 解决方案 > 如何构建一个通用类来管理对象类型的不同过程

问题描述

我有很多处理列表procedure of objectfunction of object类型的通用代码。我想将此代码标准化为通用类,以便共享公共代码。

如果我有:

type
  TNotifyEvent = procedure (Sender: TObject) of object;
  TNotifyIntEvent = procedure (Sender: TObject; Value: Integer): Boolean of object;

然后我可以拥有:

type
  TCallbackList = class(TList<TNotifyEvent>)
  end;
  TIntCallbackList = class(TList<TIntNotifyEvent>)
  end;

但是由于我在这里有更多的代码而不仅仅是声明一个列表,我希望能够像这样将整个类声明为 Generic:

type
  TFunctionManager<T: procedure of object> = class(TList<T>)
  public
    procedure Subscribe(fnCallback: T);
    procedure FireCallbacks();
    procedure Unsubscribe(fnCallback: T);
    // ... and so on
  end;

但是泛型不允许将泛型类型参数限制为procedure of object

有没有办法实现这种功能?

标签: delphigenerics

解决方案


我找到了一种方法来实现我所需要的。考虑问题很明显,拥有procedure of object类型的全部意义在于您可以调用它。要调用它,您需要知道函数的原型,因此使用函数原型的泛型的基本前提过于局限。

为了说明这一点:在问题的示例中,如果方法FireCallbacks调用列表中的所有方法,那么如果回调的原型发生更改,则FireCallbacks需要更改的原型。因此,TFunctionManager<TNotifyIntEvent>我需要声明procedure FireCallbacks(Value: Int);以便我有足够的参数来调用例程。

由于procedure of object类型存储在内存中,TMethod无论方法的原型是什么,我们都可以构建一个通用类来存储procedure of objectss 并处理公共代码,但我们将不得不重新引入一些依赖于原型的例程。

当然,例程的参数可以是不同类型的,并且可以将它们设为通用,但我们不能将参数的数量或它们的顺序设为通用。

我实现了使用通用类来实现通用代码的目标,支持不同的函数原型,方法是实现基类TNotifyEvent(任何procedure of object类型都可以,但这对我的要求很有意义),然后使用泛型类型的派生类来支持具有不同签名的回调。

下面的代码提供了我如何实现它的基本原理(没有显示所有代码,只是与在函数原型上使用泛型相关的位,但如果你想这样做,它应该足以让你开始):

interface

type
  TCallbackManager = class(TObject)
  protected
    _pCallbacks: TList<TNotifyEvent>;
  public
    constructor Create();
    destructor Destry(); override;

    procedure FireCallbacks(); virtual;
    procedure Subscribe(fnCallback: TNotifyEvent); virtual;
    procedure Unsubscribe(fnCallback: TNotifyEvent); virtual;
  end;

  T1ParamCallback<TParam1> = procedure(Sender: TObject; p1: TParam1) of Object;
  T1ParamCallbackManager<TParam1> = class(TCallbackManager)
  public
    procedure FireCallbacks(p1: TParam1); reintroduce;
    procedure Subscribe(fnCallback: T1ParamCallbackManager<TParam1>); reintroduce;
    procedure Unsubscribe(fnCallback: T1ParamCallbackManager<TParam1>); reintroduce;
  end;

  T2ParamCallback<TParam1, TParam2> = procedure(Sender: TObject; p1: TParam1; p2: TParam2) of Object;
  T2ParamCallbackManager<TParam1, TParam2> = class(TCallbackManager)
  public
    procedure FireCallbacks(p1: TParam1; p1: TParam2); reintroduce;
    procedure Subscribe(fnCallback: T2ParamCallbackManager<TParam1, TParam2>); reintroduce;
    procedure Unsubscribe(fnCallback: T2ParamCallbackManager<TParam1, TParam2>); reintroduce;
  end;

implementation

{ TCallbackManager }

constructor TCallbackManager.Create;
begin
  Self._pCallbacks:=TList<TNotifyEvent>.Create;
  inherited;
end;

destructor TCallbackManager.Destroy;
begin
  FreeAndNil(Self._pCallbacks);
  inherited;
end;

procedure TCallbackManager.FireCallbacks();
var
  fnCallback: TNotifyEvent;
begin
  for fnCallback in Self._pCallbacks do
    if(Assigned(fnCallback)) then
      fnCallback(Self);
end;

procedure TCallbackManager.Subscribe(fnCallback: TNotifyEvent);
begin
  if(not(Self._pCallbacks.Contains(fnCallback))) then
    Self._pCallbacks.Add(fnCallback);
end;

procedure TCallbackManager.Unsubscribe(fnCallback: TNotifyEvent);
begin
  Self._pCallbacks.Remove(fnCallback);
end;

{ T1ParamCallbackManager }

procedure T1ParamCallbackManager.FireCallbacks(p1: TParam1);
var
  fnCallback: TNotifyEvent;
begin
  for fnCallback in Self._pCallbacks do
    if(Assigned(fnCallback)) then
      T1ParamCallback<TParam1>(fnCallback)(Self, p1);
end;

procedure T1ParamCallbackManager.Subscribe(fnCallback: T1ParamCallback<TParam1>);
begin
  inherited Subscribe(TNotifyEvent(fnCallback));
end;

procedure T1ParamCallbackManager.Unsubscribe(fnCallback: TNotifyEvent);
begin
  inherited Unsubscribe(TNotifyEvent(fnCallback));
end;

{ T2ParamCallbackManager }

procedure T2ParamCallbackManager.FireCallbacks(p1: TParam1; p2: TParam2);
var
  fnCallback: TNotifyEvent;
begin
  for fnCallback in Self._pCallbacks do
    if(Assigned(fnCallback)) then
      T2ParamCallback<TParam1, TParam2>(fnCallback)(Self, p1, p2);
end;

procedure T2ParamCallbackManager.Subscribe(fnCallback: T2ParamCallback<TParam1, TParam2>);
begin
  inherited Subscribe(TNotifyEvent(fnCallback));
end;

procedure T2ParamCallbackManager.Unsubscribe(fnCallback: T2ParamCallback<TParam1, TParam2>);
begin
  inherited Unsubscribe(TNotifyEvent(fnCallback));
end;


推荐阅读