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

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

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

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

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

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

5 ответов


метаклассы и"процедуры класса"

метаклассы - это все о "процедурах класса". Начиная с базового class:

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

, потому что DoSomething это class procedure мы можем назвать его без экземпляра 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;

введем class of тип:

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 может использовать любую вариацию алгоритма, пока она является потомком Талгоритма.

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

// 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 необходимо, чтобы эта работа была Метаклассом для TCollectionItem.


Да, коллекция все равно сможет содержать любой потомок 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, где бы они ни были объявлены.


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

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


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

метаклассы-это не тот термин, с которым я знаком в кругах Дельфи. Я считаю, что мы называем их ссылками на класс, у которых есть менее "волшебное" звучащее имя, поэтому здорово, что вы ставите оба общих прозвища в своем вопросе.

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


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

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