[ top ] [ prev ] [ up ] [ next ]
[Delphi][PofEAA] Plugin Factory
class の依存性を除去します。
{ 適用前 }
uses uCustomer;
var
Customer: ICustomer;
{ if production then }
Customer := TCustomer.Create;
{ if test then }
Customer := TCustomerMock.Create;
{ 適用後 }
uses uPluginFactory;
var
Customer: ICustomer;
Customer := PluginFactory.GetPlugin('ICustomer') as ICustomer;
production.properties:
ICustomer=TCustomer
test.properties:
ICustomer=TCustomerMock
interface や継承を使えば constructor や setter (write メソッド) で mock object を差し込むことができます。
でもまだ問題が残っています。それは、この例のように、内部で生成する object の class を差し替えるにはどうすればいいのかという問題です。
まず思いつくのは、factory class とその interface を用意して、factory class を差し替えることで、内部で生成する object の class を差し替えるという方法です。
type
ICustomerFactory = interface
function CreateCustomer: ICustomer;
end;
type
TCustomerFactory = class(TInterfacedObject, ICustomerFactory)
public
function CreateCustomer: ICustomer;
end;
{ TCustomerFactory }
function TCustomerFactory.CreateCustomer: ICustomer;
begin
Result := TCustomer.Create;
end;
type
TCustomerMockFactory = class(TInterfacedObject, ICustomerFactory)
public
function CreateCustomer: ICustomer;
end;
{ TCustomerMockFactory }
function TCustomerMockFactory.CreateCustomer: ICustomer;
begin
Result := TCustomerMock.Create;
end;
この方法は間違いなく動作します。でも、Create するだけなのに、いちいち factory を書くのは面倒です。
というわけで、ここでは Plugin Factory というパターンを使います。
TPersistent な class だけ扱えたらいいということにして、FindClass 関数を使って Plugin Factory を実装します。
FindClass 関数は、class 名を使って class を検索します。
unit uPluginFactory;
interface
uses SysUtils, Classes, uInterfacedPersistentRC;
type
TPluginFactory = class
private
FProperties: TStringList;
public
constructor Create;
destructor Destroy; override;
function GetPlugin(const InterfaceName: string): TInterfacedPersistentRC;
end;
var
PluginFactory: TPluginFactory;
implementation
{ TPluginFactory }
constructor TPluginFactory.Create;
function GetExeName: string;
begin
Result := ParamStr(0);
end;
var
ExeName: string;
begin
FProperties:=TStringList.Create;
ExeName:=GetExeName;
FProperties.LoadFromFile( ExtractFilePath(Exename) + ChangeFileExt(ExtractFileName(Exename), '.properties') );
end;
destructor TPluginFactory.Destroy;
begin
FProperties.Free;
end;
function TPluginFactory.GetPlugin(const InterfaceName: string): TInterfacedPersistentRC;
var
ClassName: string;
AClass: TInterfacedPersistentRCClass;
begin
ClassName := FProperties.Values[ InterfaceName ];
AClass := TInterfacedPersistentRCClass(FindClass(ClassName));
Result := TInterfacedPersistentRC(AClass.Create);
end;
initialization
PluginFactory:=TPluginFactory.Create;
finalization
PluginFactory.Free;
end.
GetPlugin の内部では、プロパティファイル (実行ファイル名の拡張子を .properties に置き換えた名前のファイルを用意します) を基に interface 名から class 名を得て、FindClass 関数を呼んでいます。
ここで出てくる TInterfacedPersistentRC は TInterfacedPersistent の替わりとなる class です。
TInterfacedPersistent は reference count (RC) を無効にしていて都合が悪いので、
TInterfacedObject を参考にして、TPersistent から継承した class TInterfacedPersistentRC を新たに用意しました。
unit uInterfacedPersistentRC;
interface
uses Windows, SysUtils, SysConst, Classes;
type
TInterfacedPersistentRC = class(TPersistent, IInterface)
protected
FRefCount: Integer;
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
public
constructor Create; virtual;
procedure AfterConstruction; override;
procedure BeforeDestruction; override;
class function NewInstance: TObject; override;
property RefCount: Integer read FRefCount;
end;
TInterfacedPersistentRCClass = class of TInterfacedPersistentRC;
implementation
constructor TInterfacedPersistentRC.Create;
begin
end;
procedure TInterfacedPersistentRC.AfterConstruction;
begin
// Release the constructor's implicit refcount
InterlockedDecrement(FRefCount);
end;
procedure TInterfacedPersistentRC.BeforeDestruction;
begin
if RefCount <> 0 then
raise EInvalidPointer.CreateRes(@SInvalidPointer);
end;
// Set an implicit refcount so that refcounting
// during construction won't destroy the object.
class function TInterfacedPersistentRC.NewInstance: TObject;
begin
Result := inherited NewInstance;
TInterfacedPersistentRC(Result).FRefCount := 1;
end;
function TInterfacedPersistentRC.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
if GetInterface(IID, Obj) then
Result := 0
else
Result := E_NOINTERFACE;
end;
function TInterfacedPersistentRC._AddRef: Integer;
begin
Result := InterlockedIncrement(FRefCount);
end;
function TInterfacedPersistentRC._Release: Integer;
begin
Result := InterlockedDecrement(FRefCount);
if Result = 0 then
Destroy;
end;
end.
FindClass で見つけられるように、対象となる class の initialization 節では、RegisterClass 関数を使ってクラスを登録しておきます。
uCustomer.pas:
initialization
RegisterClass(TCustomer);
uCustomerMock.pas:
initialization
RegisterClass(TCustomerMock);
参考文献
開発環境
[ top ] [ prev ] [ up ] [ next ]