36
28

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

「DIコンテナのテスト以外での利点について」の自分の感想

Last updated at Posted at 2020-07-15

自分の書いた DI に関する記事の「DI (依存性注入) って何のためにするのかわからない人向けに頑張って説明してみる」に対する、はてなブックマークのコメントで

いや、違くない?この説明だとFactoryでいいという話になるよね(筆者もテストのMockingが容易ならDIいらないと言ってしまってるのでその認識っぽい)。
重要なのは依存を隠蔽することだよね。どっかで記事書こう

というコメントがあって実際にコメントをくれた人が以下の記事を書いてくれました。

DIコンテナのテスト以外での利点について

依存仕様の隠蔽(記事の著者の人の定義した言葉)で述べられているメリットで (swift の細かい文法わからないので C# で書くと…) 以下のように Service クラスのコンストラクターに DI コンテナを直で渡すと Service を作る人は DI コンテナさえ渡せばよくて、Service がだれに依存しているかという Service を使う人にとっては本来不要な情報を隠せて便利といってます。 (ざっくりとした私の解釈なので元記事を読むのをお勧め)

public interface IUserRepository { ... }

public class UserRepositoryImpl : IUserRepository { ... }

public class Service
{
  private readonly IUserRepository _repo;
  public Service(ISomeDIContainer container) 
  {
     // Service がだれに依存しているかは Service の内部に隠す
     _repo = container.Resolve<IUserRepository>();
  }
  public void Execute() { ... }
}

// Service を使う人はこうすればいい
var container = ... (どうにかして DI コンテナのインスタンスは取ってくる);
var service = new Service(contaienr); // Service がだれに依存しているかなんて気にしなくていい!

感想

普通にコンストラクター引数で依存関係を明示して Service も DI コンテナに生成してもらえば Service 使う人は依存先とか気にしないでいいので、もともとのデメリットというものが、そもそも発生しないと感じました。

public interface IUserRepository { ... }

public class UserRepositoryImpl : IUserRepository { ... }

public class Service
{
  private readonly IUserRepository _repo;
  public Service(IUserRepository repo) 
  {
     _repo = repo;
  }
  public void Execute() { ... }
}

// Service を使う人はこうすればいい
var container = ... (どうにかして DI コンテナのインスタンスは取ってくる);
var service = container.Resolve<Service>(); // Service がだれに依存いしているかなんて気にしなくていい!

普通に DI コンテナを使うようなフレームワークを使っていると自分で明示的に DI コンテナからインスタンスを取得する処理自体書かなくていいことがほとんど(C#, Java の Web アプリフレームワークってそういうのが多いよね?) なので、例えば HTTP リクエストを受け取る XxxController クラスではコンストラクター引数に指定しておけば、別の場所(ASP.NET Core だと Startup.cs)で書かれた DI コンテナへの型の登録に従って組み立てられたインスタンスが勝手に入ってきます。

XxxController.cs
public class XxxController : Controller
{
  private readonly Service _service;
  public XxxController(Service service) // 勝手に DI される
  {
    _service = service;
  }

  [HttpGet]
  public ActionResult<SomeResponse> Get()
  {
    // 使えばいい
    _service.Execute();
    ...(省略)...
  }
}

例え Service クラスの依存先が増えたとしても Controller はコンストラクター引数で Service が欲しい!ということだけ書いておけば、Service の依存先がなんだろうと気にする必要はありません。

まとめ

ということで、個人的な感想では「依存仕様の隠蔽」で言われるデメリットというのは、もともと発生しないのではないかという考えです。たとえファクトリーだとしても Service を組み立ててくれる人に依存先も併せて組み立ててもらうようにしておけば、Service を使う人は気にしなくていいと思います。

public static ServiceFactory
{
  public Service MakeService() => new Service(RepositoryFactory.MakeUserRepository());
}

// Service を使う人はファクトリーにお願いする
var service = ServiceFacotry.MakeService();
service.Execute();

さらに DI コンテナをコンストラクター引数に渡すということは Service や全てのクラスが特定の DI コンテナに依存してしまうので取り回しがきかなくなり (別の DI コンテナにできないなど) 辛さが増すなと感じました。

そもそも、DI コンテナからインスタンスを取得するコードを色々なところで書かないといけない状況というのは、フレームワーク側か、使ってるフレームワークがサポートしてないなら自作の仕組みを1つ噛ませて、量産するコードでは DI コンテナを触らなくても済むような仕組みを入れておいた方が DI コンテナに依存しなくなり幸せになります。

別の DI コンテナにしたいときとかも少ない痛みで移行可能です。

36
28
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
36
28

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?