読者です 読者をやめる 読者になる 読者になる

Delphiでインターフェースを使う

Delphi

久々にDelphiを少し触ってました。かなり忘れてる。
Delphiのインターフェースを使って2つの実装クラスを作り、実行時にどちらのクラスを使うか決定してる。
試したバージョンはDelphi XE4。

Main.dpr

program Main;
{$APPTYPE CONSOLE}

uses
  System.Classes,
  System.SysUtils,
  System.Generics.Collections;

type
  IStore = interface
  { データを読み書きするインターフェース }
    procedure SetValue(Key, Value: String);
    function GetValue(Key: String): String;
  end;

  TStore = class(TInterfacedObject, IStore)
  { データを読み書きするベースクラス }
  public
    procedure SetValue(Key, Value: String); virtual; abstract;
    function GetValue(Key: String): String; virtual; abstract;
  end;

  TMemoryStore = class(TStore)
  { データをメモリ上にのみ保持するクラス }
  private
    StoredData: TDictionary<String, String>;
  public
    constructor Create;
    destructor Destroy; override;
    procedure SetValue(Key, Value: String); override;
    function GetValue(Key: String): String; override;
  end;

  TFileStore = class(TStore)
  { データをファイルに保持するクラス }
  private
    Loaded: Boolean;
    FilePath: String;
    StoredData: TDictionary<String, String>;
    procedure Load;
    procedure Save;
  public
    constructor Create(FilePath: String);
    destructor Destroy; override;
    procedure SetValue(Key, Value: String); override;
    function GetValue(Key: String): String; override;
  end;

constructor TMemoryStore.Create;
begin
  WriteLn('-- TMemoryStore --');
  StoredData := TDictionary<String, String>.Create;
end;

destructor TMemoryStore.Destroy;
begin
  StoredData.Free;
end;

procedure TMemoryStore.SetValue(Key, Value: String);
begin
  StoredData.AddOrSetValue(Key, Value);
end;

function TMemoryStore.GetValue(Key: String): String;
begin
  if StoredData.ContainsKey(Key) then
    Result := StoredData.Items[Key]
  else
    Result := '';
end;

constructor TFileStore.Create(FilePath: String);
begin
  WriteLn('-- TFileStore --');
  Loaded := False;
  Self.FilePath := FilePath;
  StoredData := TDictionary<String, String>.Create;
end;

destructor TFileStore.Destroy;
begin
  Save;
  StoredData.Free;
end;

procedure TFileStore.SetValue(Key, Value: String);
begin
  { データベースの遅延読み込み }
  if not Loaded then Load;
  StoredData.AddOrSetValue(Key, Value);
end;

function TFileStore.GetValue(Key: String): String;
begin
  { データベースの遅延読み込み }
  if not Loaded then Load;
  if StoredData.ContainsKey(Key) then
    Result := StoredData.Items[Key]
  else
    Result := '';
end;

procedure TFileStore.Load;
var
  Buffer: TStringList;
  Index: Integer;
  KeyValuePair: TArray<String>;
begin
  Buffer := TStringList.Create;
  if FileExists(FilePath) then
    try
      WriteLn('Loading database...');
      Buffer.LoadFromFile(FilePath);
      for Index := 0 to Buffer.Count - 1 do
      begin
        KeyValuePair := Buffer[Index].Split([','], 2);
        StoredData.AddOrSetValue(KeyValuePair[0], KeyValuePair[1]);
      end;
    finally
      Buffer.Free;
    end;
  Loaded := True;
end;

procedure TFileStore.Save;
var
  Buffer: TStringList;
  Key: String;
begin
  Buffer := TStringList.Create;
  try
    for Key in StoredData.Keys do
    begin
      Buffer.Add(Key + ',' + StoredData.Items[Key]);
    end;
    Buffer.SaveToFile(FilePath);
  finally
    Buffer.Free;
  end;
end;

function StoreFactory(FilePath: String = ''): IStore;
begin
  { FilePathが指定されているならTFileStore、なければTMemoryStoreを使います }
  if FilePath <> '' then
    Result := TFileStore.Create(FilePath)
  else
    Result := TMemoryStore.Create;
end;

var
  Store: IStore;

begin
  { メモリリーク検出 }
  ReportMemoryLeaksOnShutdown := True;

  { コマンドライン引数の有無で実装を切り替える }
  if ParamCount > 0 then
    Store := StoreFactory(ParamStr(1))
  else
    Store := StoreFactory;

  { 値の書き込みと取得 }
  Store.SetValue('MyKey', 'MyValue');
  WriteLn(Store.GetValue('MyKey'));
end.

実行結果

C:\MyApp>dcc32 Main.dpr
Embarcadero Delphi for Win32 compiler version 25.0
Copyright (c) 1983,2013 Embarcadero Technologies, Inc.
Main.dpr(169)
170 行, 0.14 秒, コード 919344 バイト, データ 35392 バイト

C:\MyApp>Main
-- TMemoryStore --
MyValue

C:\MyApp>Main database.txt
-- TFileStore --
Loading database...
MyValue

外部入出力の部分は実装の切り替えができると、自動テストのときに便利です。DIコンテナは大仰だなと思ったら、このぐらいでもいいかもしれませんね。
また、TStoreはTInterfacedObjectを継承しているので、明示的にFreeを呼ばなくても、開放されるようにしています。