C#er、Delphiを学ぶ③（Delphiで依存性の注入やってみる）

# はじめに
Delphiを始めてちょうど１か月くらい経った。慣れたようで慣れないようで...なわけだが、一番思うのは「WEB上のDelphi関連の記事が少なすぎる」という問題である。とりわけ設計にまつわる記事をほぼほぼ見かけず、これじゃWell Architectなコード書けんやろと思う。そんな中、Delphi沼にちょいと足を踏み込んだ身として何かしら貢献できたらなぁーと思い、修行がてら書こうと思ったわけで。

# 仕様
はじめての「でるふぁいででぃーあい」といったところで、まずは単純な感じ。DIコンテナは使わず、コンストラクタにインターフェースを注入する、いわゆる「Poor man's DI」での実装。

- ファイル（a.txt）から最初の１行を読みとる
- 読み取った内容を違うファイル（b.txt）に書き込む

# インターフェースの実装方法について
エンバカの公式サイト（docwiki）みたら、インターフェースにGUIDついてた。しかも「TInterfacedObject」なるものも継承してる。なにこれ超怖い、わからない。検索したら、Delphiでのインターフェースの実装方法にまつわる以下の記事が出てきて参考になった。

- [インターフェース使っててハマりそうなところをつらつらと](https://qiita.com/ktz_alias/items/d9d7dcc60c76b79d71e3)
- http://delphimaniacs.blogspot.com/2013/12/delphi-interface.html

なるへそ。要は凝ったことしない限りGUIDつけんでもいいが、付けといた方が無難といったところか。ショートカット「Ctrl + Alt + G」でGUIDを追加ヨシッと。Visual Studioと同じキーの割り当て設定してるよね？（タイトル回収）

# 実装
## FileReaderクラス
ファイルを読み込む役割を担うクラス。GetContent関数の中で、ファイルの１行目の内容を返してくれる。

```pascal:FileReader.pas
unit FileReader;

interface

uses
	System.Classes;

type
	IFileReader = interface
		['{FA610526-A7B4-4C44-AB9D-143B50578D48}']
		function GetContent(): string;
	end;

	TFileReader = class(TInterfacedObject, IFileReader)
	private
		FData: TStream;
		FDataReader: TStreamReader;
	public
		constructor Create(AFileName: string);
		destructor Destroy; override;
		function GetContent(): string;
	end;

implementation

uses
	System.SysUtils;

constructor TFileReader.Create(AFileName: string);
begin
	inherited Create;
	FData := TFileStream.Create(AFileName, fmOpenRead);
	FDataReader := TStreamReader.Create(FData);
end;

destructor TFileReader.Destroy;
begin
	FreeAndNil(FDataReader);
	FreeAndNil(FData);
	inherited;
end;

function TFileReader.GetContent(): string;
begin
	if not FDataReader.EndOfStream then
	begin
		Result := FDataReader.ReadLine;
	end;
end;

end.
```

## FileWriterクラス
ファイルへの書き込みの役割を担うクラス。Write関数内で引数で渡された内容をファイルに吐き出す。

```pascal:FileWriter.pas
unit FileWriter;

interface

uses
	System.Classes;

type
	IFileWriter = interface
		['{7C912111-3757-40DD-94FD-173377F8FE95}']
		procedure Write(AContent: string);
	end;

	TFileWriter = class(TInterfacedObject, IFileWriter)
	private
		FData: TStream;
		FDataWriter: TStreamWriter;
	public
		constructor Create(AFileName: string);
		destructor Destroy; override;
		procedure Write(AContent: string);
	end;

implementation

uses
	System.SysUtils;

constructor TFileWriter.Create(AFileName: string);
begin
	inherited Create;
	FData := TFileStream.Create(AFileName, fmCreate);
	FDataWriter := TStreamWriter.Create(FData);
end;

destructor TFileWriter.Destroy;
begin
	FreeAndNil(FDataWriter);
	FreeAndNil(FData);
	inherited;
end;

procedure TFileWriter.Write(AContent: string);
begin
	FDataWriter.WriteLine(AContent);
end;

end.
```

## FileService
コンストラクタで受け取ったReader,Writerを元に、仕様を実現する役割を担うクラス（~~命名適当や、すまん~~ ）。

```pascal:FileService.pas
unit FileService;

interface

uses
	FileWriter, FileReader;

type
	TFileService = class
	private
		FFileReader: IFileReader;
		FFileWriter: IFileWriter;
	public
		constructor Create(AFileReader: IFileReader; AFileWriter: IFileWriter);
		procedure Execute;
	end;

implementation

uses
	System.SysUtils;

constructor TFileService.Create(AFileReader: IFileReader; AFileWriter: IFileWriter);
begin
	inherited Create;
	FFileReader := AFileReader;
	FFileWriter := AFileWriter;
end;

procedure TFileService.Execute;
var
	FileContent: string;
begin
	FileContent := FFileReader.GetContent();
	FFileWriter.Write(FileContent)
end;

end.
```

## エントリポイント（呼び出し元）
Reader,Writerのインスタンスを生成し、コンストラクタに注入する。実装イメージは↓

```pascal
FileReader := TFileReader.Create('C:\tmp\a.txt');
FileWriter := TFileWriter.Create('C:\tmp\b.txt');

FileService := TFileService.Create(FileReader, FileWriter);
FileService.Execute();
```

# さいごに
こんな感じでビジネスロジックと永続化層を分離できましたとさ。一緒くたな感じでDelphiでユニットテストも書いてみたいな。メモリリークしてるやもですが許してくださいm(__)m

# 参考URL
DIとかSOLID原則で参考になるなと思った記事

- [.NETにおけるSOLID設計原則とデザインパターン](https://www.infoq.com/jp/news/2013/09/solid-principles-revisited/)