Утка печатать в Delphi 2007?

Вопрос:

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


Ресурсы Duck typing для Delphi 2010 и новее:

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

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

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


person menjaraz    schedule 29.02.2012    source источник


Ответы (3)


С помощью ObjAuto.pas и типов вызываемых вариантов это должно быть возможно (написано в 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;
person Stefan Glienke    schedule 29.02.2012
comment
+1: Это то, что я ожидал. Я тестировал его с помощью Delphi 7 и Delphi 2007: он работает. Этот пост - приемлемый ответ. Danke sehr! - person menjaraz; 29.02.2012
comment
+1 Аллен Бауэр уже говорил об этом раньше, например blogs.embarcadero.com/abauer/2007/06/13/36013 Но я помню, что их было больше. Или, может быть, это было в старом блоге Ника на Codegear. - person David Heffernan; 01.03.2012
comment
+1, мне это нравится. @menjaraz, вам следует подумать об изменении принятого ответа. - person Cosmin Prund; 01.03.2012
comment
@Cosmin Prund: Все в порядке с вашим соглашением. - person menjaraz; 01.03.2012
comment
@Stefan Glienke: Вы использовали его в продакшене? - person menjaraz; 01.03.2012
comment
@menjaraz: Нет, я написал это только вчера, когда увидел этот вопрос;) - person Stefan Glienke; 01.03.2012
comment
@Stefan Glienke: Не могли бы вы рассмотреть этот следующий пост. Заранее спасибо. - person menjaraz; 09.03.2012

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

Не в значительном смысле

Более длинный ответ: согласно странице вики Duck Typing идентифицируется:

При утином наборе текста важны только те аспекты объекта, которые используются, а не тип самого объекта. Например, в языке без типа утки можно создать функцию, которая принимает объект типа Duck и вызывает методы walk и quack этого объекта. В языке с утиным типом эквивалентная функция будет принимать объект любого типа и вызывать методы walk и 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 (обобщенные и комплексные RTTI) вы приблизитесь к Duck typing только для ограниченной функциональности и со значительными усилиями. Этого просто нет в ДНК Delphi (потому что ДНК Delphi говорит о статической типизации), но вы могли бы получить что-то достаточно близкое, приложив много усилий и только для определенных функций. Может быть, если вы дадите нам представление о том, какая конкретная функциональность вам нужна, мы сможем что-то выяснить.

person Cosmin Prund    schedule 29.02.2012
comment
Спасибо, что ответили. Я углубился в исходные коды трех ресурсов, которые я процитировал, и пришел к такому выводу: они достигают Duck typing с использованием интерфейса, дженериков и полноценного Rtti (Duck Duck Delphi нацелен на Delphi XE2, остальное можно сделать с Delphi XE). - person menjaraz; 29.02.2012
comment
Я хочу иметь возможность манипулировать любым произвольным объектом списка, реализующим 1) function Count: Integer; 2) function GetItem(const index: Integer): TObject; 3) procedure Add(const AObject: TObject); 4) procedure Clear; в стиле Duck typing в Delphi 2007. - person menjaraz; 29.02.2012
comment
@menjaraz: Затем создайте интерфейс с этими 4 методами и используйте Supports(), чтобы проверить, реализует ли объект списка этот интерфейс. - person mghie; 29.02.2012
comment
@mghie: это предотвращает манипуляции (как в стиле Duck) другими объектами, реализовавшими перечисленные мной методы, но без интерфейса. Я прав? - person menjaraz; 29.02.2012
comment
@menjaraz: Верно, но будет ли это проблемой? - person mghie; 29.02.2012
comment
@menjaraz: Чтобы сформулировать это по-другому: почему вы предпочитаете утиный ввод (небезопасно), а не приведение к интерфейсу (или проверку того, реализует ли объект интерфейс перед его преобразованием), тем самым сохраняя безопасность типов, предоставляемую с помощью Delphi? Для меня это идеальный способ заняться только теми аспектами объекта, которые используются, а не типом самого объекта. У вас никогда не было перегруженных методов? - person mghie; 01.03.2012
comment
@mghie: см. запись в блоге Даниэле Тети . Я не хочу вводить зависимость от Superlayer type для любого типа списка, который я должен использовать в своих бизнес-объектах. Этого можно добиться с помощью DORM. - person menjaraz; 01.03.2012

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

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

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

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

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

person Warren P    schedule 29.02.2012
comment
+1. Это кажется действительно очень долгим путем. Я попытался реализовать это как тест в консольном приложении и потерпел неудачу (сдался, когда понял, что мне нужна зарегистрированная библиотека типов), но мои знания COM и OLE довольно близки к нулю. И с другой стороны, ваша функция не имеет возвращаемого типа :) - person Cosmin Prund; 29.02.2012
comment
Delphi позволяет очень легко писать двойные типы, и внутри приложения вам нужна библиотека типов, но вы можете зарегистрировать ее локально, без необходимости регистрировать ее вне Delphi, используя SideBySide (sxs) COM. Под Уродливым, да, я имею в виду долгий путь, но это действительно работает. Однако, если бы кто-нибудь захотел сделать это в приложении, к которому я имел какое-то отношение, я бы сразу отказался. Но поскольку он отвечает потребностям ОП, по крайней мере, строго говоря, это вариант. - person Warren P; 29.02.2012
comment
+1, но я бы убрал первое предложение. В этом нет ничего уродливого (если кто-то решил использовать утиную печать, что является чуждой концепцией в Delphi). Я бы предпочел, чтобы это был принятый ответ ... - person mghie; 29.02.2012