【问题标题】:What is the correct way to structure this generic object creation构造此通用对象创建的正确方法是什么
【发布时间】:2017-02-28 11:26:38
【问题描述】:

忽略它使用 Aurelius 框架的事实,这个问题更多的是关于我需要如何重新调整代码以使通用构造函数注入对这两种类型都有效:



还要忽略子对象在同一个单元中的事实,我通常会将它们放在自己的单元中,但这只是为了更容易在问题中发布。

我正在尝试使用工厂方法模式来确定它应该在运行时建立什么类型的连接,具体取决于我实例化的对象。 目前它正在硬编码它在创建时期望的链接类型。

在示例中,我想传入一个 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)

在父级 TModelDatabaseConnection 中使用抽象的“GetAdapterClass”类型函数,并在子级中声明适配器的类以执行以下操作:

LAdapter := GetAdapterClass.Create...

适配器声明的一个例子是

TFireDacConnectionAdapter = class(TDriverConnectionAdapter<TFDConnection>, IDBConnection)

【问题讨论】:

  • 您使用的是 Spring.Persistence 适配器吗?因为这段代码对我来说很熟悉。如果是这样,请查看 Spring.Persistence.Core.ConnectionFactory.pas 如何为适配的连接注入不同的 ctor 参数。
  • 嘿斯特凡。老实说,这些实际上是来自 TMS Aurelius 框架的那些。不过,我也会看看 Spring 单元,因为我的 PC 上也有它们,看看它是否能给我更多线索。
  • 嗨斯特凡。在 Spring.Persistence.Adapters.SQLite 中,您指定必须传入 TSQLiteDatabase 而不仅仅是文件名。我可以在 Spring 代码中找到的唯一一个是 ..\Marshmallow\External\SQLite3 下的那个。这是“官方”春季版吗?
  • 适配器总是让他们的适配器通过。但是由于它们的实现,被适配者的 ctor 参数可能会有所不同。这是由知道如何解析不同参数的 DI 容器或我在上一条评论中提到的单元处理的。至于 SQLite3 单元 - 它们修复了一些错误,但它们不是官方的,因为我们不为它们提供任何支持、维护或责任。

标签: delphi dependency-injection delphi-10-seattle delphi-10.1-berlin


【解决方案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 分支。

【讨论】:

    猜你喜欢
    • 2017-12-26
    • 1970-01-01
    • 2015-05-22
    • 2013-10-15
    • 1970-01-01
    • 1970-01-01
    • 2019-04-02
    • 2016-05-22
    相关资源
    最近更新 更多