Универсальная фабрика - PullRequest
2 голосов
/ 09 июля 2009

предположим, у меня есть TModel:

TModelClass = class of TModel;
TModel = class
  procedure DoSomeStuff;
end;

и 2 потомка:

TModel_A = class(TModel);
TModel_B = class(TModel);

и фабрика:

TModelFactory = class
  class function CreateModel_A: TModel_A;
  class function CreateModel_B: TModel_B;
end;

Теперь я хочу немного изменить рефакторинг:

TModelFactory = class
  class function CreateGenericModel(Model: TModelClass) : TModel
end;

class function TModelFactory.CreateGenericModel(Model: TModelClass) : TModel
begin
  ...
  case Model of
    TModel_A: Result := TModel_A.Create;
    TModel_B: Result := TModel_B.Create;
  end;
  ...
end;

Пока это нормально, но каждый раз, когда я создаю потомка TModel, мне приходится изменять фабричный оператор case.

Мой вопрос: возможно ли создать 100% универсальную фабрику для всех моих TModel потомков, поэтому каждый раз, когда я создаю TModel потомков, мне не нужно изменять TModelFactory?

Я пытался поиграть с дженериками Delphi 2009, но не нашел ценной информации, все они связаны с базовым использованием TList<T> и т. Д.

Обновление Извините, но, может быть, я не ясен или не понимаю ваш ответ (я все еще новичок), но я пытаюсь достичь:

var
  M: TModel_A;
begin
  M: TModelFactory.CreateGenericModel(MY_CONCRETE_CLASS);

Ответы [ 6 ]

6 голосов
/ 09 июля 2009

Ну, вы могли бы написать

class function TModelFactory.CreateGenericModel(AModelClass: TModelClass): TModel;
begin
  Result := AModelClass.Create;
end;

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

Edit:

Чтобы ответить на ваш комментарий о том, как добавлять новые классы без необходимости изменять фабрику - я дам вам простой пример кода, который работает для очень старых версий Delphi, Delphi 2009 должен найти гораздо лучшие способы сделать это.

Каждый новый класс-потомок должен быть зарегистрирован только на фабрике. Один и тот же класс может быть зарегистрирован с использованием нескольких идентификаторов. Код использует строковый идентификатор, но целые числа или идентификаторы GUID будут работать так же хорошо.

type
  TModelFactory = class
  public
    class function CreateModelFromID(const AID: string): TModel;
    class function FindModelClassForId(const AID: string): TModelClass;
    class function GetModelClassID(AModelClass: TModelClass): string;
    class procedure RegisterModelClass(const AID: string;
      AModelClass: TModelClass);
  end;

{ TModelFactory }

type
  TModelClassRegistration = record
    ID: string;
    ModelClass: TModelClass;
  end;

var
  RegisteredModelClasses: array of TModelClassRegistration;

class function TModelFactory.CreateModelFromID(const AID: string): TModel;
var
  ModelClass: TModelClass;
begin
  ModelClass :=  FindModelClassForId(AID);
  if ModelClass <> nil then
    Result := ModelClass.Create
  else
    Result := nil;
end;

class function TModelFactory.FindModelClassForId(
  const AID: string): TModelClass;
var
  i, Len: integer;
begin
  Result := nil;
  Len := Length(RegisteredModelClasses);
  for i := 0 to Len - 1 do
    if RegisteredModelClasses[i].ID = AID then begin
      Result := RegisteredModelClasses[i].ModelClass;
      break;
    end;
end;

class function TModelFactory.GetModelClassID(AModelClass: TModelClass): string;
var
  i, Len: integer;
begin
  Result := '';
  Len := Length(RegisteredModelClasses);
  for i := 0 to Len - 1 do
    if RegisteredModelClasses[i].ModelClass = AModelClass then begin
      Result := RegisteredModelClasses[i].ID;
      break;
    end;
end;

class procedure TModelFactory.RegisterModelClass(const AID: string;
  AModelClass: TModelClass);
var
  i, Len: integer;
begin
  Assert(AModelClass <> nil);
  Len := Length(RegisteredModelClasses);
  for i := 0 to Len - 1 do
    if (RegisteredModelClasses[i].ID = AID)
      and (RegisteredModelClasses[i].ModelClass = AModelClass)
    then begin
      Assert(FALSE);
      exit;
    end;
  SetLength(RegisteredModelClasses, Len + 1);
  RegisteredModelClasses[Len].ID := AID;
  RegisteredModelClasses[Len].ModelClass := AModelClass;
end;
5 голосов
/ 09 июля 2009

Решение с Model.Create работает, если конструктор является виртуальным.

Если вы используете Delphi 2009, вы можете использовать другой трюк, используя дженерики:

type 
  TMyContainer<T: TModel, constructor> (...)
  protected
    function CreateModel: TModel;
  end;

function TMyContainer<T>.CreateModel: TModel;
begin
  Result := T.Create; // Works only with a constructor constraint.   
end;
5 голосов
/ 09 июля 2009
Result := Model.Create;

тоже должно работать.

4 голосов
/ 10 июля 2009

Если я правильно понимаю ваш вопрос, я написал что-то подобное здесь http://www.malcolmgroves.com/blog/?p=331

2 голосов
/ 09 июля 2009

Вероятно, есть более простой способ сделать это. Кажется, я помню, что нашел встроенный объект TClassList, который обрабатывал это, но в этот момент у меня уже была эта работа. TClassList не имеет способа поиска сохраненных объектов по имени строки, но он все еще может быть полезен.

В основном, чтобы это работало, вам нужно зарегистрировать ваши классы с глобальным объектом. Таким образом, он может принять строковый ввод для имени класса, найти это имя в списке, чтобы найти правильный объект класса.

В моем случае я использовал TStringList для хранения зарегистрированных классов, и я использую имя класса в качестве идентификатора класса. Чтобы добавить класс в член «object» списка строк, мне нужно было обернуть класс в реальный объект. Я признаю, что я действительно не понимаю «класс», так что это может не понадобиться, если вы разыгрываете все правильно.

  // Needed to put "Class" in the Object member of the
  // TStringList class
  TClassWrapper = class(TObject)
  private
    FGuiPluginClass: TAgCustomPluginClass;
  public
    property GuiPluginClass: TAgCustomPluginClass read FGuiPluginClass;
    constructor Create(GuiPluginClass: TAgCustomPluginClass);
  end;

У меня есть глобальный объект "PluginManager". Здесь классы регистрируются и создаются. Метод «AddClass» помещает класс в TStringList, чтобы я мог посмотреть его позже.


procedure TAgPluginManager.AddClass(GuiPluginClass: TAgCustomPluginClass);
begin
  FClassList.AddObject(GuiPluginClass.ClassName,
    TClassWrapper.Create(GuiPluginClass));
end;

В каждом создаваемом классе я добавляю его в список классов в разделе «Инициализация».


initialization;
  AgPluginManager.AddClass(TMyPluginObject);

Затем, когда придет время создать класс, я смогу найти имя в списке строк, найти класс и создать его. В моей действительной функции я проверяю, чтобы убедиться, что запись существует и имеет дело с ошибками и т. Д. Я также передаю дополнительные данные в конструктор класса. В моем случае я создаю формы, поэтому я на самом деле не возвращаю объект обратно вызывающей стороне (я отслеживаю их в своем PluginManager), но это будет легко сделать при необходимости.


procedure TAgPluginManager.Execute(PluginName: string);
var
  ClassIndex: integer;
  NewPluginWrapper: TClassWrapper;
begin
    ClassIndex := FClassList.IndexOf(PluginName);
    if ClassIndex > -1 then
    begin
      NewPluginWrapper := TClassWrapper(FClassList.Objects[ClassIndex]);
      FActivePlugin := NewPluginWrapper.GuiPluginClass.Create();
    end;
end;

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

Чтобы создать объект, я просто вызываю


  PluginManger.Execute('TMyPluginObject');
1 голос
/ 20 августа 2014

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

type
  TViewFactory = TGenericFactory<Integer, TMyObjectClass, TMyObject>;
...
F := TViewFactory.Create;
F.ConstructMethod :=
  function(AClass: TMyObjectClass; AParams: array of const): TMyObject
  begin
    if AClass = nil then
      Result := nil
    else
      Result := AClass.Create;
  end;

и единица измерения для фабрики:

unit uGenericFactory;

interface

uses
  System.SysUtils, System.Generics.Collections;

type
  EGenericFactory = class(Exception)
  public
    constructor Create; reintroduce;
  end;

  EGenericFactoryNotRegistered = class(EGenericFactory);
  EGenericFactoryAlreadyRegistered = class(EGenericFactory);

  TGenericFactoryConstructor<C: constructor; R: class> = reference to function(AClass: C; AParams: array of const): R;

  TGenericFactory<T; C: constructor; R: class> = class
  protected
    FType2Class: TDictionary<T, C>;
    FConstructMethod: TGenericFactoryConstructor<C, R>;
    procedure SetConstructMethod(const Value: TGenericFactoryConstructor<C, R>);
  public
    constructor Create(AConstructor: TGenericFactoryConstructor<C, R> = nil); reintroduce; overload; virtual;
    destructor Destroy; override;

    procedure RegisterClass(AType: T; AClass: C);
    function ClassForType(AType: T): C;
    function TypeForClass(AClass: TClass): T;
    function SupportsClass(AClass: TClass): Boolean;
    function Construct(AType: T; AParams: array of const): R;
    property ConstructMethod: TGenericFactoryConstructor<C, R> read FConstructMethod write SetConstructMethod;
  end;

implementation

uses
  System.Rtti;

{ TGenericFactory<T, C, R> }

function TGenericFactory<T, C, R>.ClassForType(AType: T): C;
begin
  FType2Class.TryGetValue(AType, Result);
end;

function TGenericFactory<T, C, R>.Construct(AType: T; AParams: array of const): R;
begin
  if not Assigned(FConstructMethod) then
    Exit(nil);

  Result := FConstructMethod(ClassForType(AType), AParams);
end;

constructor TGenericFactory<T, C, R>.Create(AConstructor: TGenericFactoryConstructor<C, R> = nil);
begin
  inherited Create;
  FType2Class := TDictionary<T, C>.Create;
  FConstructMethod := AConstructor;
end;

destructor TGenericFactory<T, C, R>.Destroy;
begin
  FType2Class.Free;
  inherited;
end;

procedure TGenericFactory<T, C, R>.RegisterClass(AType: T; AClass: C);
begin
  if FType2Class.ContainsKey(AType) then
    raise EGenericFactoryAlreadyRegistered.Create;
  FType2Class.Add(AType, AClass);
end;

procedure TGenericFactory<T, C, R>.SetConstructMethod(const Value: TGenericFactoryConstructor<C, R>);
begin
  FConstructMethod := Value;
end;

function TGenericFactory<T, C, R>.SupportsClass(AClass: TClass): Boolean;
var
  Key: T;
  Val: C;
begin
  for Key in FType2Class.Keys do
    begin
      Val := FType2Class[Key];
      if CompareMem(@Val, AClass, SizeOf(Pointer)) then
        Exit(True);
    end;

  Result := False;
end;

function TGenericFactory<T, C, R>.TypeForClass(AClass: TClass): T;
var
  Key: T;
  Val: TValue;
begin
  for Key in FType2Class.Keys do
    begin
      Val := TValue.From<C>(FType2Class[Key]);
      if Val.AsClass = AClass then
        Exit(Key);
    end;

  raise EGenericFactoryNotRegistered.Create;
end;

{ EGenericFactory }

constructor EGenericFactory.Create;
begin
  inherited Create(Self.ClassName);
end;

end.
...