処理の委譲とかDIについての記事をいくつか読んだので、具体的な実装を追って理解を深めてみたいと思います。
DIについては軽く触れる程度となります。
複雑なコードができてしまう事例
例として、APIを呼び出してその結果を取得する処理を書いてみましょう。
IEnumerator GetItems()
{
var url = "https://qiita.com/api/v2/items";
var www = new WWW(url);
yield return www;
var result = Json.Deserialize(www.text);
Debug.Log(result[0]["title"]);
}
戻り値のJsonをパースして、最初の記事のタイトルを出力する処理ができました。
さて、ここで戻り値の型がMessagePackの場合もあるとします。シンボルを定義して処理の書き分けをしてみましょう。
IEnumerator GetItems()
{
...
#if MSGPACK_API
var result = Msgpack.Deserialize(www.text);
#else
var result = Json.Deserialize(www.text);
#endif
Debug.Log(result[0]["title"]);
}
なんと、本番環境では戻り値をgzip圧縮して暗号化することにする、と連絡がありました。対応してみましょう。
IEnumerator GetItems()
{
...
#if MSGPACK_API
var bytes = www.bytes;
#if RELEASE_MODE
bytes = GZipDecode(Decrypt(bytes));
#endif
var result = Msgpack.Deserialize(bytes);
#else
var result = Json.Deserialize(www.text);
#endif
Debug.Log(result[0]["title"]);
}
……往々にして、このようなコードが出来上がることは良くあります。
処理の委譲
さて、ここで処理の委譲を活用していきましょう。
WWW
を引数にresult
を取り出す処理が複雑になってしまっていたので、そこをインターフェース化します。
interface IDeserializer
{
APIResult Deserialize(WWW www);
}
戻り値がJsonの場合、MessagePackの場合、本番環境の場合の3通りのDeserializer
を作ってみます。
class JsonDeserializer : IDeserializer
{
public APIResult Deserialize(WWW www)
{
return Json.Deserialize(www.text);
}
}
class MsgPackDeserializer : IDeserializer
{
public APIResult Deserialize(WWW www)
{
return Msgpack.Deserialize(www.bytes);
}
}
class APIDeserializer : IDeserializer
{
public APIResult Deserialize(WWW www)
{
var bytes = GZipDecode(Decrypt(www.bytes));
return Msgpack.Deserialize(www.bytes);
}
}
IDeserializer
を利用して、先ほどの処理を書き直してみましょう。
IDeserializer deserializer;
IEnumerator GetItems()
{
...
var result = deserializer.Deserialize(www.text);
Debug.Log(result[0]["title"]);
}
#if
による処理の分岐が消えてスッキリしました!
また、JsonやMessagePack、gzip展開などの処理はIDeserializerが行うことになったため、GetItems()
はそれらに依存しなくなりました。
DIについて
先ほどの例ではdeserializer
の初期化が省略されていました。これはどこかで定義しておく必要があります。
deserializer
を代入する処理まで書いた例を見てみましょう。
class APIClient
{
public static IDeserializer deserializer;
public IEnumerator GetItems() { ... }
}
class Program : MonoBehaviour
{
public void Start()
{
#if RELEASE_MODE
APIClient.deserializer = new APIDeserializer();
#elif MSGPACK_API
APIClient.deserializer = new MsgpackDeserializer();
#else
APIClient.deserializer = new JsonDeserializer();
#endif
var client = new APIClient();
StartCoroutine(client.GetItems())
}
}
deserializer
を外部から代入できるようにして、初期化時に利用するIDeserializer
を選択して代入しています。
実はこれはDIパターンとして成立していて、フレームワークなどは利用していませんがDIを実現しています。
このように、場合によって処理が変わる、依存関係をなくして疎結合にするといったことを実現するためにDIが利用されます。
Unityの場合DIフレームワークとしてZenjectというものがあり、単純なDIの他にも便利な機能があるので使い方を覚えてみるのも良いと思います。