4. Logicクラスを切り直す。単一責任原則およびレイヤアーキテクチャを意識して。
放置していたLogic部分を修正してみた。方向性は以下。
- ビジネスロジック層(Bl)とデータアクセス層(Dao)を分離。
- データアクセス部分は差し替えや変更がしばしばあるため、部品化しておくと幸せになりやすい。
- ユニットテストを作る際にデータアクセス部分はモックで差し替えられたほうが絶対楽。
- UI側とやり取りするのはビジネスロジック層の窓口クラスに限定。
- UIと各モデルが自由に直接やり取りすると、eventを通じたやり取りが複雑化する。収拾がつかなくなると最悪なため、カオスになる前に制約をかけておく。
- ついでに汎用性が高いメソッドをUtilitiesとして切り出した。
ビジネスロジック層
using System;
public class Number
{
public int Value { get; private set; }
public Number(int number)
{
Value = number;
}
public void Pow()
{
Value = (int) Math.Pow(Value, 2);
}
public void Increment()
{
Value++;
}
public void Decrement()
{
Value--;
}
}
class NumberMediator
{
public delegate void NumberChangedEventHandler(int sender);
public event NumberChangedEventHandler Calculated;
Number Number;
public void Pow(int number)
{
Number = new Number(number);
Number.Pow();
var dao = new NumberFile("pow");
dao.Save(Number);
Calculated(Number.Value);
}
public void Increment(int number)
{
Number = new Number(number);
Number.Increment();
var dao = new NumberFile("increment");
dao.Save(Number);
Calculated(Number.Value);
}
public void Decrement(int number)
{
Number = new Number(number);
Number.Decrement();
var dao = new NumberFile("decrement");
dao.Save(Number);
Calculated(Number.Value);
}
}
var daoと書いている部分は、「読み書きするクラス」の良い名前が思いつかずこうしている。個人的に微妙と感じるところである。読み込むだけならloaderなりreaderなりで書けるのだが。
データアクセス層
保存先を切り替えたりモックで差し替えたりするかもしれない(というかする)、という理由によりインターフェイスも切った。ただしこれを利用している窓口クラスがnew で呼び出してしまっているので、今はほぼ意味がない。
必要性が生じたときに切ればいい話ではないのかという説もある :p
public interface INumberDao
{
void Save(Number number);
}
using System;
using System.IO;
public class NumberFile : INumberDao
{
private string Filename;
public NumberFile(string nameKey)
{
Filename = Config.PersistentDataPath + "/" + nameKey + ".txt";
}
public void Save(Number number)
{
File.Write(Filename, number.Value.ToString());
}
}
ユーティリティ
public static class File
{
public static void Write(string filePath, string contents)
{
using (var fileStream = new FileStream(filePath, System.IO.FileMode.Create, System.IO.FileAccess.Write, System.IO.FileShare.None))
{
using (var writer = new StreamWriter(fileStream))
{
writer.Write(contents);
}
}
}
}
以上。
窓口となるクラス名が変わったため呼び出す側のViewも変わっているが、クラス名が変わっただけなので省略。
よくあるレイヤアーキテクチャである。分離する前よりもコード量は増えているわけだが、レイヤを分ける以上オーバヘッドが生じるのは仕方がない。それよりAdaptiveにできるメリットのほうが大きい。
その1のViewクラス一つのときと比べ、一見すると処理があちこちに飛んだ分わかりづらくなったようにも見えるかもしれないが、いざユニットテストを書いたり仕様変更に対応することになったりしたときに真価が発揮されるのである(たぶん)。
大分改善されたと思うが、まだ課題は残っている。
- 生のeventとdelegateを使っているが、UniRxを使えないだろうか。
- NumberMediatorでDaoを呼ぶときにnewを使っているが、これでは差し替えるのが大変である。DiまたはFactoryで書き換えることはできないだろうか。
というわけでまだもう少し続く。