DI(Dependency Injection) vs Factory Pattern?
DI(Dependency Injection) vs Factory Pattern などと検索すると、例えば下記のような記事が見つかります。
この記事では車のエンジンとホイールとタイヤを使って両者の違いを説明しています。
例がSwiftじゃなくてすいません。
DIの例
class Car
{
private Engine;
private SteeringWheel;
private Tires tires;
public Car(Engine engine, SteeringWheel wheel, Tires tires)
{
this.Engine = engine;
this.SteeringWheel = wheel;
this.Tires = tires;
}
}
Factory Pattern
static class CarFactory
{
public ICar BuildCar()
{
Engine engine = new Engine();
SteeringWheel steeringWheel = new SteeringWheel();
Tires tires = new Tires();
ICar car = new RaceCar(engine, steeringWheel, tires);
return car;
}
}
違いの説明としては悪くありません。
しかしながら、実際の設計に適用しようと思うとちょっと違和感があります。
ちょっと違和感が・・・
まず、みなさんはエンジンとホイールとタイヤを車に乗る度に交換しますか?
もちろんそのような交換が必要なレース用の車では話が違いますが、一般のドライバーはそんなことはしないでしょう。また、車に乗るのではなく、車の生産や車検や定期的な交換だと言われるかも知れませんが、そのように滅多に起きることのない事象に対してDIはいささか大げさだと思ってしまうのです。
車の生産や車検の時に、つまり単体テストする場合に依存するインスタンスをテスト用に変更する程度であればDI構造を取らなくてもFactory Patternで十分だと考えます。
もちろんDIの方が柔軟であることに間違いはありません。大きな違いは依存するインスタンスの生成などのソースコードが外部に存在することで、ソースコードの追跡が一度途切れてしまうことです。
SwiftであればProtocolを使ってDIすることが多いでしょうが、DIされたインスタンスの実体の(classなどの)宣言にジャンプすることは簡単ではありません。特にリバースエンジニアリングのような場合は、実際には何のインスタンスが渡ってきているかを掴むのが少々面倒臭くなる場合もあります。一方Factory PatternであればFactoryのコード内に即時にジャンプしてインスタンスの実態を掴むことが容易になります。
前任者がドキュメントもコメントも書かず書き散らかしたソースコードを追跡する羽目になることは日常茶飯事ですが、そんな時にあらゆる場面でDIされてしまうと、1つだけならちょっと面倒なだけのこの追跡で後任者は頭を掻きむしりたくなることでしょう。
一方で、例えば車ではなく、電子レンジのようなオブジェクトを考えた場合、レシピと食材を投入して料理が調理されて出てくる、といった機能に対しては「DIしないでどうする!」と思います。
より柔軟なインスタンスの入れ替えをとるか、コードの追跡性を取るか、これはケースバイケースだと思います。
レイヤード設計におけるDI
MVVMやMVC構造の場合は、その良し悪しは別としても基本的には上位の層が下位の層に依存するように設計されます。「依存関係逆転の原則」なるものもありますが、これは例えばデバイスドライバーのフレームワークを作って各デバイスに対するデバイスドライバーをそのフレームワークに合わせ込むと行った場面ではとても有効な概念だと思います。しかしながら、例えばiOSアプリのMVVMやMVC構造ではあまり必要な考え方だとは思いません。
(実際には「依存性逆転の原則」は上位層と下位層の依存関係を逆転しろと言っているわけではなく、どちらも抽象に依存しろ、と言っているわけですが現実にはその抽象をどちらが管理するかで少し様相は変わってきます)
もちろん各層の間でProtocolベースでインターフェイスすべきことは言うまでもありませんが、例えばView層をViewModel層にDIする、といった設計は有効なのでしょうか?
MVVMやMVC構造では、各層のロジックが下層に行くに連れてより普遍的になり、変更・修正の確率が下がって行くことを前提としています。変更される可能性が高いオブジェクトを可能性が低いオブジェクトに依存させることで、上位層の変更の範囲を限定的にする、というのがこの考え方でしょう。
つまりProtocolでDIされるViewインスタンスの設計は変更される可能性が高く、Protocol自体も変更される可能性が高いのです。
変更された場合はViewModelの内部コードに影響を与えるということになります。このことは本来分離すべく設計していた下位層まで巻き込んで、大掛かりな改修になってしまう可能性が増大することを意味します。
ViewとViewModelの関係は、カーネルのmodule仕様とデバイスドライバーの関係とは全く違います。
つまりここでもケースバイケースで考えるべきだと思います。
DIはとっても素敵な考え方ですが、なんでもかんでも適用しようと思うのはちょっと待ってください、
もしやるのなら自分で最後まで責任を持ってくださいね、と言いたいのです。