Утиная печать в Delphi 2007?

Вопрос:

Есть ли способ сделать утиную печать с Delphi 2007 (т.е. без дженериков и расширенных функций Rtti)?


Duck typing Ресурсы для Delphi 2010:

Последнее изменение:

Я углубился в перечисленные выше ресурсы и изучил все опубликованные ответы здесь.

В конечном итоге я уточнил свое требование, сделав последующий пост на этот вопрос.

Ответ 1

С помощью типов ObjAuto.pas и invokable вариантов это должно быть возможно (написано в XE, но должно также работать в Delphi 7 или ниже):

unit DuckTyping;

interface

function Duck(Instance: TObject): Variant;

implementation

uses
  ObjAuto,
  SysUtils,
  TypInfo,
  Variants;

type
  TDuckVarData = packed record
    VType: TVarType;
    Reserved1, Reserved2, Reserved3: Word;
    VDuck: TObject;
    Reserved4: LongWord;
  end;

  TDuckVariantType = class(TPublishableVariantType)
  protected
    function GetInstance(const V: TVarData): TObject; override;
  public
    procedure Clear(var V: TVarData); override;
    procedure Copy(var Dest: TVarData; const Source: TVarData;
      const Indirect: Boolean); override;
    function DoFunction(var Dest: TVarData; const V: TVarData;
      const Name: string; const Arguments: TVarDataArray): Boolean; override;
  end;

var
  DuckVariantType: TDuckVariantType;

{ TDuckVariantType }

procedure TDuckVariantType.Clear(var V: TVarData);
begin
  V.VType := varEmpty;
  TDuckVarData(V).VDuck := nil;
end;

procedure TDuckVariantType.Copy(var Dest: TVarData; const Source: TVarData;
  const Indirect: Boolean);
begin
  if Indirect and VarDataIsByRef(Source) then
    VarDataCopyNoInd(Dest, Source)
  else
  begin
    with TDuckVarData(Dest) do
    begin
      VType := VarType;
      VDuck := TDuckVarData(Source).VDuck;
    end;
  end;
end;

function TDuckVariantType.DoFunction(var Dest: TVarData; const V: TVarData;
  const Name: string; const Arguments: TVarDataArray): Boolean;
var
  instance: TObject;
  methodInfo: PMethodInfoHeader;
  paramIndexes: array of Integer;
  params: array of Variant;
  i: Integer;
  ReturnValue: Variant;
begin
  instance := GetInstance(V);
  methodInfo := GetMethodInfo(instance, ShortString(Name));
  Result := Assigned(methodInfo);
  if Result then
  begin
    SetLength(paramIndexes, Length(Arguments));
    SetLength(params, Length(Arguments));
    for i := Low(Arguments) to High(Arguments) do
    begin
      paramIndexes[i] := i + 1;
      params[i] := Variant(Arguments[i]);
    end;

    ReturnValue := ObjectInvoke(instance, methodInfo, paramIndexes, params);
    if not VarIsEmpty(ReturnValue) then
      VarCopy(Variant(Dest), ReturnValue);
  end
  else
  begin
    VarClear(Variant(Dest));
  end;
end;

function TDuckVariantType.GetInstance(const V: TVarData): TObject;
begin
  Result := TDuckVarData(V).VDuck;
end;

function Duck(Instance: TObject): Variant;
begin
  TDuckVarData(Result).VType := DuckVariantType.VarType;
  TDuckVarData(Result).VDuck := Instance;
end;

initialization
  DuckVariantType := TDuckVariantType.Create;

finalization
  FreeAndNil(DuckVariantType);

end.

Вы можете просто использовать его так:

type
  {$METHODINFO ON}
  TDuck = class
  public // works in XE, not sure if it needs to be published in older versions
    procedure Quack;
  end;

procedure TDuck.Quack;
begin
  ShowMessage('Quack');
end;

procedure DoSomething(D: Variant);
begin
  D.Quack;
end;

var
  d: TDuck;
begin
  d := TDuck.Create;
  try
    DoSomething(Duck(d));
  finally
    d.Free;
  end;
end;

Ответ 2

Быстрый ответ:

Не значимым образом

Более длинный ответ: согласно странице wiki "Утиная печать" обозначается:

При наборе текста утка речь идет только о тех аспектах используемого объекта, а не о типе самого объекта. Например, на языке, не относящемся к уткам, можно создать функцию, которая принимает объект типа Duck и вызывает методы обхода и смещения объекта. В языке с утиным языком эквивалентная функция будет принимать объект любого типа и называть методы объекта walk and quack. Если объект не имеет методов, которые вызывают, тогда функция сигнализирует ошибку времени выполнения.

Эквивалентный не компилируемый код Delphi будет выглядеть следующим образом:

procedure DoSomething(D);
begin
  D.Quack;
end;

Я намеренно не указал тип для D, потому что это победит цель. Delphi статически типизирован, поэтому это никогда не сработает. Если вам это нужно для небольшой функциональности, вы можете использовать Interfaces или RTTI и получить что-то вроде этого:

procedure DoSomething(D:TObject);
begin
  (D as ISomeIntf).Quack;
end;

Если вы можете получить RTTI:

procedure DoSomething(D:TObject);
begin
  CallQuackUsingRTTI(D);
end;

Я лично использовал метод RTTI для идентификации (и манипулирования) объектов списка таким образом, чтобы код работал как с потомками TList, так и с общими TList<T> вариантами.

Отвлечься от этого должно быть: Даже с расширенными функциональными возможностями в новейших версиях Delphi (generics и всеобъемлющей RTTI) вы только приблизитесь к набору Duck для ограниченной функциональности и со значительными усилиями. Это просто не в ДНК Delphi (потому что Delphi DNA говорит "статическая типизация" ), но вы можете получить что-то достаточно близко и с большим трудом и только для определенной функциональности. Может быть, если вы дадите нам представление о том, какую конкретную функциональность вы хотите, мы сможем что-то понять.

Ответ 3

Вот идея, которая требует создания библиотеки типов.

Используйте типы автоматизации OLE и реализуйте интерфейсы отправки (два COM-объекта).

Теперь вы теперь можете писать все, что захотите, после этого типа, и мы будем выяснять во время работы, работает ли оно или нет, или взрывается. Добро пожаловать в динамическую типизацию.

procedure DoSomething(D:OleVariant);
begin
  D.Quack; // Might work, might blow up.
end;

Я считаю это уродливым, но другие могут не быть.