コンテンツにスキップ

最終更新:2026-10-12

C++ 依存性注入

最近、星球で依存性注入についての更新のリクエストをもらったので、この記事を書きました。実際の例から解説し、本記事ではいくつかの原理と実装を説明します。完全な実装コードの省略版は星球に掲載しています。それでは本題に入りましょう。

目次:

  • 直接依存
  • インターフェースによる依存性注入
  • テンプレートによる依存性注入

依存性注入はよく使われるデザインパターンで、通常はインターフェースやテンプレートの方式によってコードのテスト容易性と保守性を高めます。以下、例を挙げてみていきます。

ユーザーサービスクラスUserServiceがあり、HTTPクライアントを通じてリモートサーバーと通信する必要があるとします。この場合、次の三つの実装が書かれるかもしれません。

1.直接依存

UserServiceの内部がHttpClientに直接依存する例です:

class HttpClient {
 public:
  std::string Get(const std::string& url);
  void Post(const std::string& url, const std::string& data);
};

class UserService {
 private:
  HttpClient m_httpClient;
};

UserServiceの内部には通常、m_httpClientを操作するいくつかのメソッドがあります。この方式はシンプルで荒っぽいのですが、UserServiceが具体的なHttpClientと結合してしまい、依存オブジェクトの差し替えが難しく、単体テストも行いにくくなります。

そこから、次の二つの方法が導かれます。

2.インターフェースによる依存性注入

私たちの目標は、テストの際に具体的なクラスを生成しなくても簡単にテストできることです。ユーザーがクラスをmockしてテストできるようにするため、インターフェースのことを思いつきます。HttpClientを抽出すると、次のようになります:

struct IHttpClient {
  virtual std::string Get(const std::string& url) = 0;
  virtual void Post(const std::string& url, const std::string& data) = 0;
  virtual ~IHttpClient() = default;
};
class UserService {
 private:
  std::unique_ptr<IHttpClient> m_httpClient;
};

こうすれば、テストのときにclientをmockしてテストできます。例えば:

class MockHttpClient : public IHttpClient {
public:
  MOCK_METHOD(std::string, Get, (const std::string& url), (override));
  MOCK_METHOD(void, Post, (const std::string& url, const std::string& data), (override));
};

auto mockHttpClient = std::make_unique<MockHttpClient>();
UserService userService(std::move(mockHttpClient));

以上の改造を経て、次のことが達成できます:

  • .cc ファイルに実装の詳細を隠す
  • 抽象インターフェースに対してプログラミングする
  • オブジェクトの構築中に依存項目を注入する

3.テンプレートによる依存性注入

テンプレートによる依存性注入は、C++のテンプレート技術を通じて、依存オブジェクトをテンプレート引数としてUserServiceに渡します。テンプレートをインスタンス化するときに、具体的なHTTPクライアントの実装を指定します。この方法は依存の問題をコンパイル時に解決し、仮想関数の呼び出しが不要なため、性能が向上します。同時に、UserServiceクラスは異なるHTTPクライアント実装と柔軟に統合でき、単体テストも容易です。

template <typename THttpClient>
class UserService {
 private:
  THttpClient m_httpClient;
};

この形であればテストがしやすくなります。例えば:

UserService<MockHttpClient> userService;

この方式には次のような利点があります:

  • テンプレートのインスタンス化の間に依存項目を注入する
  • 仮想呼び出しが不要(THttpClient はインターフェースではないため、呼び出しはコンパイル時に解決できる点に注意)

以上が本節で説明するすべての内容です。本節の完全なコードは星球に掲載しています。ぜひ購読してダウンロードしてください!

関連トピック

コメント