Ссылки класса Delphi … иначе метаклассы …, когда использовать их

Я прочитал официальную документацию, и я понимаю то, что ссылки класса всего лишь, мне не удается видеть, когда и почему они - лучшее решение по сравнению с альтернативами.

Примером, приведенным в документации, является TCollection, который можно инстанцировать с любым потомком TCollectionItem. Выравнивание для использования ссылки класса состоит в том, что оно позволяет Вам вызывать методы класса, тип которых неизвестен во время компиляции (я предполагаю, что это - время компиляции TCollection). Я просто не вижу, как с помощью TCollectionItemClass, поскольку аргумент превосходит использование TCollectionItem. TCollection все еще смог бы содержать любого потомка TCollectionItem и все еще смочь вызвать любой метод, объявленный в TCollectionItem. Не был бы он?

Сравните это с универсальным набором. TObjectList, кажется, предлагает почти такую же функциональность как TCollection с дополнительным преимуществом безопасности типов. Вы освобождены от требования для наследования TCollectionItem для хранения типа объекта, и можно сделать набор как тип конкретным, как Вы хотите. И если необходимо получить доступ к участникам объекта из набора, можно использовать универсальные ограничения. Кроме того, что ссылки класса доступны программистам до Delphi, 2009 является там каким-либо другим неопровержимым доводом для использования их по универсальным контейнерам?

Другой пример, данный в документации, передает ссылку класса функции, которая действует как объектная фабрика. В этом случае фабрика для создания объектов типа TControl. Не действительно очевидный, но я предполагаю, что фабрика TControl вызывает конструктора порожденного типа, переданного ему, а не конструктора TControl. Если это верно, затем я начинаю видеть по крайней мере некоторые основания для использования ссылок класса.

Таким образом, я предполагаю то, что я действительно пытаюсь понять, когда и то, где ссылки класса являются самыми соответствующими и что они покупают разработчика?

14
задан Kenneth Cochran 30 July 2010 в 15:29
поделиться

5 ответов

МетаКлассы и «процедуры класса»

Все метаклассы связаны с «процедурами класса». Начиная с базового класса :

type
  TAlgorithm = class
  public
    class procedure DoSomething;virtual;
  end;

Поскольку DoSomething является процедурой класса , мы можем вызывать ее без экземпляра TAlgorithm (она ведет себя как любая другая глобальная процедура ). Мы можем сделать это:

TAlgorithm.DoSomething; // this is perfectly valid and doesn't require an instance of TAlgorithm

Как только мы получим эту настройку, мы можем создать несколько альтернативных алгоритмов, все из которых будут разделять некоторые биты и части базового алгоритма. Примерно так:

type
  TAlgorithm = class
  protected
    class procedure DoSomethingThatAllDescendentsNeedToDo;
  public
    class procedure DoSomething;virtual;
  end;

  TAlgorithmA = class(TAlgorithm)
  public
    class procedure DoSomething;override;
  end;

  TAlgorithmB = class(TAlgorithm)
  public
    class procedure DoSomething;override;
  end;

Теперь у нас есть один базовый класс и два дочерних класса. Следующий код вполне допустим, потому что мы объявили методы как методы «класса»:

TAlgorithm.DoSomething;
TAlgorithmA.DoSomething;
TAlgorithmB.DoSomething;

Давайте представим класс типа :

type
  TAlgorithmClass = class of TAlgorithm;

procedure Test;
var X:TAlgorithmClass; // This holds a reference to the CLASS, not a instance of the CLASS!
begin
  X := TAlgorithm; // Valid because TAlgorithmClass is supposed to be a "class of TAlgorithm"
  X := TAlgorithmA; // Also valid because TAlgorithmA is an TAlgorithm!
  X := TAlgorithmB;
end;

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

procedure CrunchSomeData(Algo:TAlgorithmClass);
begin
  Algo.DoSomething;
end;

CrunchSomeData(TAlgorithmA);

В этом примере процедура CrunchSomeData может использовать любой вариант алгоритма, если он является потомком TAlgorithm.

Вот пример того, как это поведение может быть использовано в реальном приложении: Представьте себе приложение для расчета заработной платы, в котором некоторые числа должны быть рассчитаны в соответствии с алгоритмом, определенным законом. Вполне возможно, что этот алгоритм со временем изменится, потому что Закон несколько раз менялся. Нашему приложению необходимо рассчитать заработную плату как за текущий год (с помощью современного калькулятора), так и за другие годы, используя старые версии алгоритма. Вот как это может выглядеть:

// Algorithm definition
TTaxDeductionCalculator = class
public
  class function ComputeTaxDeduction(Something, SomeOtherThing, ThisOtherThing):Currency;virtual;
end;

// Algorithm "factory"
function GetTaxDeductionCalculator(Year:Integer):TTaxDeductionCalculator;
begin
  case Year of
    2001: Result := TTaxDeductionCalculator_2001;
    2006: Result := TTaxDeductionCalculator_2006;
    else Result := TTaxDeductionCalculator_2010;
  end;
end;

// And we'd use it from some other complex algorithm
procedure Compute;
begin
  Taxes := (NetSalary - GetTaxDeductionCalculator(Year).ComputeTaxDeduction(...)) * 0.16;
end;

Виртуальные конструкторы

Конструктор Delphi работает так же, как «функция класса»; Если у нас есть метакласс, и метакласс знает о виртуальном конструкторе, мы можем создавать экземпляры типов-потомков. Это используется редактором IDE TCollection для создания новых элементов, когда вы нажимаете кнопку «Добавить». Все, что требуется TCollection для этой работы, - это MetaClass для TCollectionItem.

22
ответ дан 1 December 2019 в 07:38
поделиться

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

Конечно, это также может быть реализовано с помощью метода класса, возвращающего соответствующий класс формы, но для этого потребуется, чтобы класс формы был известен классу. Мой подход делает систему более модульной. Форма должна знать класс, но не наоборот. Это может быть ключевым моментом, когда вы не можете изменить классы.

0
ответ дан 1 December 2019 в 07:38
поделиться

Да, коллекция по-прежнему сможет содержать любого потомка TCollectionItem и вызывать для него методы. НО, он не сможет создать новый экземпляр любого потомка TCollectionItem. Вызов TCollectionItem.Create создает экземпляр TCollectionItem, тогда как

private
  FItemClass: TCollectionItemClass;
...

function AddItem: TCollectionItem;
begin
  Result := FItemClass.Create;
end;

создает экземпляр любого класса потомка TCollectionItem, который содержится в FItemClass.

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

Например, наблюдаемый потомок TObjectList (или общий контейнер) может иметь что-то вроде:

function AddItem(aClass: TItemClass): TItem;
begin
  Result := Add(aClass.Create);
  FObservers.Notify(Result, cnNew);
  ...
end;

Короче говоря, преимущество / преимущество метаклассов заключается в том, что любой метод / класс, имеющий только знание

type
  TMyThing = class(TObject)
  end;
  TMyThingClass = class of TMyThing;

, может создавать экземпляры любой потомок TMyThing, где бы они ни были объявлены.

8
ответ дан 1 December 2019 в 07:38
поделиться

Дженерики очень полезны, и я согласен, что TObjectList (обычно) полезнее, чем TCollection. Но ссылки на классы более полезны для разных сценариев. Они действительно являются частью другой парадигмы. Например, ссылки на классы могут быть полезны, когда у вас есть виртуальный метод, который нужно переопределить. Переопределения виртуальных методов должны иметь ту же сигнатуру, что и оригинальные, поэтому парадигма Generics здесь не применима.

Одно из мест, где ссылки на классы активно используются, - это потоковая передача форм. Просмотрите как-нибудь DFM в виде текста, и вы увидите, что на каждый объект ссылаются по имени и классу. (Причем имя необязательно.) Когда устройство чтения форм читает первую строку определения объекта, оно получает имя класса. Он ищет его в таблице поиска, извлекает ссылку на класс и использует эту ссылку на класс для вызова переопределения TComponent.Create(AOwner: TComponent), чтобы создать нужный тип объекта, а затем начинает применять свойства, описанные в DFM. Именно такие вещи дают ссылки на классы, и их нельзя сделать с помощью дженериков.

4
ответ дан 1 December 2019 в 07:38
поделиться

Я бы также использовал метакласс всякий раз, когда мне нужно иметь возможность создать фабрику, которая может конструировать не только один жестко закодированный класс, но и любой класс, который наследуется от моего базового класса.

Метаклассы - это не тот термин, с которым я знаком в кругах Delphi. Я полагаю, что мы называем их ссылками на классы, что имеет менее "магическое" звучание, поэтому здорово, что вы включили в свой вопрос оба распространенных названия.

Конкретный пример того, где я видел, как это хорошо используется, - в компонентах JVCL JvDocking, где компонент "стиль стыковки" предоставляет информацию о метаклассе базовому набору компонентов стыковки, так что когда пользователь перетаскивает мышь и пристыковывает клиентскую форму к форме стыковочного узла, формы "tab host" и "conjoin host", которые показывают панели захвата (похожие по виду на панель заголовка обычного незакрепленного окна), могут быть определенного пользователем класса плагина, который обеспечивает настраиваемый внешний вид и настраиваемую функциональность во время выполнения, на основе плагина.

1
ответ дан 1 December 2019 в 07:38
поделиться
Другие вопросы по тегам:

Похожие вопросы: