Как я могу проверить, поддерживает ли что-то универсальный интерфейс?

#delphi #generics #interface

#delphi #дженерики #интерфейс

Вопрос:

Я использую Delphi XE2. В настоящее время у меня есть объектно-ориентированная модель, и каждый объект модели может иметь несколько валидаторов. Вот упрощенная реализация универсального абстрактного класса validator. Конкретные классы валидатора могут переопределять DoValidate, и им не нужно приводить объект модели. Средство проверки используется с использованием его интерфейса IValidator.

 unit ObjectBasedValidator;

interface

uses
  System.SysUtils,
  System.Generics.Collections;

type
  TModelEntity = class
  end;

type
  IValidator = interface
    procedure Validate(aEntity: TModelEntity; aResult: string);
  end;

  TValidator<T: TModelEntity> = class(TInterfacedObject, IValidator)
  private
  protected
    procedure DoValidate(aEntity: T; aResult: string); virtual; abstract;
  public
    procedure Validate(aEntity: TModelEntity; aResult: string);
  end;

implementation

{ TValidator<T> }

procedure TValidator<T>.Validate(aEntity: TModelEntity; aResult: string);
begin
  if not (aEntity is T) then
    Exit;

  DoValidate(aEntity as T, aResult);
end;

end.
 

Теперь я пытаюсь изменить объектную модель на интерфейсную. Итак, вот обновленный модуль проверки:

 unit InterfaceBasedValidator;

interface

type
  IModelEntity = interface
  end;

type
  IValidator = interface
    procedure Validate(aEntity: IModelEntity; aResult: string);
  end;

  TValidator<I: IModelEntity> = class(TInterfacedObject, IValidator)
  private
  protected
    procedure DoValidate(aEntity: I; aResult: string); virtual; abstract;
  public
    procedure Validate(aEntity: IModelEntity; aResult: string);
  end;

implementation

{ TValidator<T> }

procedure TValidator<I>.Validate(aEntity: IModelEntity; aResult: string);
begin
  // The next line does not compiles
  if not (aEntity is I) then
    Exit;

  DoValidate(aEntity as I, aResult);
end;

end.
 

Я добавляю комментарий к строке, которая не компилируется. Теперь, очевидно, что для универсального типа «I» должен быть определен GUID, чтобы это работало, однако нет способа указать это требование в качестве ограничения.

Возможным обходным решением может быть отказ от использования общего абстрактного класса и приведения интерфейса в валидаторе, но мне просто интересно, есть ли у кого-нибудь идея, как это сделать без приведения.

Комментарии:

1. Чтобы это сработало, я думаю, должен быть какой-то способ указать ограничение has GUID .

Ответ №1:

Кажется, работает следующее:

 uses
  SysUtils, TypInfo;

{ TValidator<I> }

procedure TValidator<I>.Validate(const aEntity: IModelEntity; aResult: string);
var
  intf: I;
begin
  if not Supports(aEntity, GetTypeData(TypeInfo(I))^.Guid, intf) then
    Exit;

  DoValidate(intf, aResult);
end;
 

Комментарии:

1. Какой режим сбоя, когда I у него нет GUID?

2. Идентификатор GUID равен нулю ({00000000-0000-0000-0000-000000000000}), но, что интересно, поддержка по-прежнему возвращает True.

3. Можно только догадываться о том, что появится intf в этом случае.

4. Мне пришлось бы пройти через код RTL, чтобы быть уверенным, но я бы предположил, что запрос нулевого GUID вернет указатель общего IInterface/IUnknown интерфейса, если вообще что-нибудь.