Каков правильный способ структурировать создание этого универсального объекта?

Игнорируя тот факт, что здесь используется Aurelius Framework, этот вопрос больше о том, как мне нужно перенастроить код, чтобы внедрение универсального конструктора работало для обоих типов:
‹ string >
и
‹ TCustomConnection >
Также игнорируйте тот факт, что дочерние объекты находятся в одном и том же блоке, я бы обычно помещал их в свои собственные, но это просто упрощает публикацию в вопросе.

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

В этом примере я хочу передать TModelDatabaseLink, но хочу решить во время выполнения, какой тип соединения с базой данных может быть, или исходит ли соединение с базой данных из файла. Да, я знаю, что могу раскомментировать FFilename и просто использовать его для хранения версии имени файла, но мне всегда интересно узнать немного больше.

unit Model.Database.Connection;

interface

uses
  System.Classes,
  Data.DB,
  Aurelius.Drivers.Interfaces,
  Aurelius.Engine.DatabaseManager;

type
  TModelDatabaseLink<T> = class
  private
    //FFilename: string;
    FConnection: T;
    FOwnsConnection: boolean;
    OwnedComponent: TComponent;
  end;

  TModelDatabaseConnection = class abstract
  private
    FDatabaseLink: TModelDatabaseLink<TCustomConnection>;
    FDatabaseManager: TDatabaseManager;
    FConnection: IDBConnection;
    function CreateConnection: IDBConnection; virtual; abstract;
    procedure CreateDatabaseManager;
  public
    constructor Create(ADatabaseLink: TModelDatabaseLink<TCustomConnection>);
    destructor Destroy; override;

    property Connection: IDBConnection read FConnection;
  end;

  TSQLLiteConnection = class(TModelDatabaseConnection)
  private
    function CreateConnection: IDBConnection; override;
  end;

  TFireDacConnection = class(TModelDatabaseConnection)
  private
    function CreateConnection: IDBConnection; override;
  end;

implementation

uses
  System.SysUtils,

  Aurelius.Drivers.Base,
  Aurelius.Drivers.SQLite,
  Aurelius.Drivers.FireDac,

  FireDAC.Stan.Intf, FireDAC.Stan.Option,
  FireDAC.Stan.Error, FireDAC.UI.Intf, FireDAC.Phys.Intf, FireDAC.Stan.Def,
  FireDAC.Stan.Pool, FireDAC.Stan.Async, FireDAC.Phys, FireDAC.VCLUI.Wait,
  FireDAC.Comp.Client;

{ TModelDatabaseConnection }

constructor TModelDatabaseConnection.Create(ADatabaseLink: TModelDatabaseLink<TCustomConnection>);
begin
  FDatabaseLink := ADatabaseLink;
  FConnection := CreateConnection;
  if Assigned(FConnection) then
    CreateDatabaseManager
  else
    raise Exception.Create('Failed to open database');
end;

procedure TModelDatabaseConnection.CreateDatabaseManager;
begin
  FDatabaseManager := TDatabaseManager.Create(FConnection);
end;

destructor TModelDatabaseConnection.Destroy;
begin
  FDatabaseManager.Free;
  FDatabaseLink.Free;
  inherited Destroy;
end;

{ TSQLLiteConnection }

function TSQLLiteConnection.CreateConnection: IDBConnection;
var
  LFilename: String;
  LAdapter: TSQLiteNativeConnectionAdapter;
begin
  //LFileName := FDatabaseLink.FConnection;                     << needs to be type string
  LAdapter := TSQLiteNativeConnectionAdapter.Create(LFilename);
  LAdapter.DisableForeignKeys;
  Result := LAdapter;
end;

{ TFireDacConnection }

function TFireDacConnection.CreateConnection: IDBConnection;
var
  LAdapter: TFireDacConnectionAdapter;
begin
  if Assigned(FDatabaseLink.OwnedComponent) then
    LAdapter := TFireDacConnectionAdapter.Create(FDatabaseLink.FConnection as TFDConnection, FDatabaseLink.OwnedComponent)
  else
    LAdapter := TFireDacConnectionAdapter.Create(FDatabaseLink.FConnection as TFDConnection, FDatabaseLink.FOwnsConnection);

  Result := LAdapter;
end;

end.

Еще одна вещь, которую я хотел бы сделать, если это возможно, это изменить два творения:

LAdapter := TSQLiteNativeConnectionAdapter.Create(LFilename)

LAdapter := TFireDacConnectionAdapter.Create(FDatabaseLink.FConnection as TFDConnection, FDatabaseLink.OwnedComponent)

использовать абстрактную функцию типа "GetAdapterClass" в родительском TModelDatabaseConnection и просто объявить класс адаптера в дочернем элементе, чтобы сделать что-то вроде:

LAdapter := GetAdapterClass.Create...

Пример объявления адаптера:

TFireDacConnectionAdapter = class(TDriverConnectionAdapter<TFDConnection>, IDBConnection)

person mikelittlewood    schedule 28.02.2017    source источник
comment
Может быть, вы работаете с адаптерами Spring.Persistence? Потому что этот код кажется мне очень знакомым. Если это так, посмотрите в Spring.Persistence.Core.ConnectionFactory.pas, как вводятся различные параметры ctor для адаптированного соединения.   -  person Stefan Glienke    schedule 28.02.2017
comment
Привет, Стефан. Честно говоря, это те, что из фреймворка TMS Aurelius. Однако я также взгляну на модуль Spring, поскольку он также есть на моем ПК, чтобы посмотреть, даст ли он мне больше подсказок.   -  person mikelittlewood    schedule 28.02.2017
comment
Привет Стефан. В Spring.Persistence.Adapters.SQLite вы указываете, что вам нужно передать TSQLiteDatabase, а не просто имя файла. Единственный, который я могу найти в коде Spring, - это тот, который находится в ..\Marshmallow\External\SQLite3. Это официальная весна?   -  person mikelittlewood    schedule 28.02.2017
comment
Адаптеры всегда передают своего адаптируемого. Но аргументы ctor адаптируемых могут различаться из-за их реализации. Это обрабатывается либо контейнером DI, который знает, как разрешать различные параметры, либо модулем, о котором я упоминал в предыдущем комментарии. Что касается модулей SQLite3 - в них исправлено несколько ошибок, но они не являются официальными, так как мы не обеспечиваем их поддержку, обслуживание или ответственность за них.   -  person Stefan Glienke    schedule 28.02.2017


Ответы (1)


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

Я копирую сюда некоторые части своего кода с корректировками:

  TServerType = (stLocal, stFireDac);

  IDatabase = interface
    function getDatabaseType: TServerType;
    property DatabaseType: TServerType read getDatabaseType;
  end;

  IAurelius = interface (IDatabase)
    ['{990BB776-2E70-4140-B118-BEFF61FDBDAF}']
    function getDatabaseConnection: IDBConnection;
    function getDatabaseManager: TDatabaseManager;
    property DatabaseConnection: IDBConnection read getDatabaseConnection;
    property DatabaseManager: TDatabaseManager read getDatabaseManager;
  end;

  IAureliusLocal = interface (IAurelius)
    ['{9F705CC4-6E3B-4706-B54A-F0649CED3A8D}']
    function getDatabasePath: string;
    property DatabasePath: string read getDatabasePath;
  end;

  IAureliusFireDac = interface (IAurelius)
    // I use this for a memory database but the logic is the same for FireDAC. You need to add the required properties 
  end;

  TAurelius = class (TInterfacedObject, IAurelius)
  private
    fServerType: TServerType;
    fDatabaseConnection: IDBConnection;
    fDatabaseManager: TDatabaseManager;
    function getDatabaseConnection: IDBConnection;
    function getDatabaseManager: TDatabaseManager;
  public
    constructor Create (const serverType: TServerType);
  end;

  TAureliusLocal = class (TAurelius, IAureliusLocal)
  private
    fDatabasePath: string;
    function getDatabasePath: string;
  public
    constructor Create (const databasePath: string);
  end;

  TAureliusFireDac = class (TAurelius, IAureliusFireDac)
  public 
    constructor Create (const aConnection: TFDConenction); <-- or whatever parameters you need here to initalise the FireDac connection
  end;

Я собираюсь пропустить здесь код для всех функций getXXX.

Конструкторы такие:

constructor TAurelius.Create(const serverType: TServerType);
begin
  inherited Create;
  fServerType:=serverType;
end;

constructor TAureliusLocal.Create (const databasePath: string);
const
  databaseFilename = 'test.sqlite';
begin
  inherited Create(stLocal);
  fDatabasePath:=Trim(databasePath);
   try
    fDatabaseConnection:=
    TSQLiteNativeConnectionAdapter.Create(
      TPath.Combine(fDatabasePath, databaseFilename));
   except
    raise Exception.Create('stLocal database can''t be created');
   end;
end;

constructor TAureliusFireDac.Create (const aConnection: TFDConenction);
begin
  inherited Create(stFireDac);
  // <-- here you initialise the connection like before but for FireDac
end;

Теперь, когда вы хотите создать базу данных Aurelius, вы используете следующие функции:

function createAureliusDatabase (const serverType: TServerType): IAurelius;
begin
  case serverType of
    stLocal: result:=TAureliusLocal.Create(path);
    stFireDac: result:=TAureliusFireDac.Create(....);
  end;
end;

... и вы просто называете это так:

var 
  currentDatabase: IAurelius;
begin
  currentDatabase:=createAureliusDatabase(stLocal,'c:\....');
end;

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

person John Kouraklis    schedule 17.03.2017