Хранение кода в коллекции: TDictionary и анонимные методы. |
- Statistics
- Participants
- Translate into Russian
- Translation result
- Translation complete.
Ранее я упоминал, что Вы можете использовать новые классы универсальных коллекций в Delphi 2009, чтобы сохранить анонимные методы вместо данных. Я хотел проверить это, потому придумал сценарий реализации классов фабрики.
Фабрики могут быть очень полезны для централизации логики создания классов. Очень часто Вы видите это, когда у Вас есть базовый класс и целый набор различных классов-потомков, и Вы хотите создавать их в одном месте. Скажем, у Вас есть приложение для создания отчетов. У Вас может быть основной отчет и набор порожденных отчетов, и Вы хотите обеспечить некоторую согласованность по значениям свойств для различных отчетов, которые установлены во время создания. Помещение их во всех возможных местах создания очета, может привести к ошибкам. В качестве альтернативы, возможно, Вы захотите уйти от огромных if…then...else блоков, которые вы можете часто видеть в такой ситуации. Фабрика отчетов предоставит вам общее место, для того чтобы удостовериться, что все отчеты создаются првильным способом, а также позволит клиентскому коду просто запросить отчет по имени и получить надлежащий конкретный класс отчета, созданный для него.
Проблему обычно составляет необходимость писать новую фабрику каждый раз она вам понадобится, плюс создание связующих классов фабрики для регистрации каждого конкретного отчета. Это не только целая куча работы и ненужных классов, это еще больше нового кода для отладки.
Вместо этого, я подумал, попытаюсь создать один класс-фабрику с помощью универсальных шаблонов (дженериков), позволяющий менять ключ для запроса экземпляра. Класс, который использует анонимные методы, чтобы избежать необходимости использовать связующие классы для создания объектов. Опираясь на приведенный выше сценарий, вот как я хотел создавать экземпляр:
ReportFactory := TFactory<string , TBaseReport>.Create;
Где первый параметр универсального шаблона - это тип ключа используемого для запроса отчета (в данном случае строка прелстваляет имя отчета), второй параметр - это базовый тип, который фабрика будет мне возвращать.
Вот как я буду регистрировать новые реализации:
ReportFactory.RegisterFactoryMethod('Malcolm''s Report',
function : TBaseReport
begin
Result := TReportFlexible.Create(rnReportMalcolm);
// остальной код установки свойств/ код настройки
end);
Вы просто передаёте ключ и анонимный метод используемый для создания экземпляра. В заключение, я хочу получить отдельный экземпляр отчета, вызывая GetInstance у фабрики и передав значение ключа.
Report := ReportFactory.GetInstance('Malcolm''s Report');
Помните, однако, тип значения ключа и базовый тип возвращенных объектов должны быть настраиваемыми через параметры универсального шаблона (дженерика).
Вот определение класса, которое я написал:
TFactoryMethod<tbasetype> = reference to function : TBaseType;
TFactory<tkey , TBaseType> = class
private
FactoryMethods : TDictionary<tkey , TFactoryMethod><tbasetype>>;
function GetCount: Integer;
public
constructor Create;
destructor Destroy; override;
property Count : Integer read GetCount;
procedure RegisterFactoryMethod(Key : TKey;
FactoryMethod : TFactoryMethod<tbasetype>);
procedure UnregisterFactoryMethod(Key : TKey);
function IsRegistered (Key : TKey) : boolean;
function GetInstance(Key : TKey) : TBaseType;
end;
Как вы можете видеть, TKey - это имя параметра универсального шаблона представляющего тип, который я хочу использовать для запроса определенного экземпляра базового типа, указанного в TBaseType. RegisterFactoryMethod дает вам зарегистрировать анонимный метод типа TFactoryMethod, который будет создавать экземпляр, также имеется соответствующий метод UnregisterFactoryMethod для удаления пары ключ/метод. Здесь GetInstance получает параметр TKey и возвращает экземпляр базового типа. Есть еще несколько методов для проверки существования ключа и метод для получения числа зарегистрированных ключей.
Если вы уже работали с универсальными шаблонами (дженериками), то ничего особенно интересного вы не найдете. Несмотря на это мне интересно хранение пар ключ/метод в TDictionary<TKey, TFactoryMethod<TBaseType>>. Хранение анонимного метода и ключа в словаре очень наглядно:
procedure TFactory<tkey , TBaseType>.RegisterFactoryMethod(Key: TKey;
FactoryMethod: TFactoryMethod<tbasetype>);
begin
if IsRegistered(Key) then
raise TFactoryMethodKeyAlreadyRegisteredException.Create('');
FFactoryMethods.Add(Key, FactoryMethod);
end;
и получить это и выполнить это равнозначно.
function TFactory<tkey , TBaseType>.GetInstance(Key: TKey): TBaseType;
var
FactoryMethod : TFactoryMethod<tbasetype>;
begin
if not IsRegistered(Key) then
raise TFactoryMethodKeyNotRegisteredException.Create('');
FactoryMethod := FFactoryMethods.Items[Key];
if Assigned(FactoryMethod) then
Result := FactoryMethod;
end;
Этим мы получаем класс-фабрику многоразового использования, потому у меня нет повода избегать их в будущем. Конечно, это довольно упрощенная реализация, она не учитывает конвейерную обработку во время процесса создания экземпляра, однако, это не сложно будет добавить для большего числа анонимных методов, например, хранимых в TList<T>.
В любом случае, надеюсь этот эксперимент пробудил несколько идей у вас. Исходный код для класса и клиента описан в виде модульных тестов и доступен тут.
Следующее мое сообщение, вероятно, будет примером Объединения в пул, который я упомянул ранее, но так или иначе забыл опубликовать.
Original (English): Storing code in a collection : TDictionary and Anonymous Methods
Translation: © r3code .
