web-dev-qa-db-ja.com

C ++で依存性注入は便利ですか

C#はDependency Injection(DI)をたくさん持つために無損失およびテスト可能プラットフォーム。このためには、interfaceと、おそらく[〜#〜] di [〜#〜]またはInversion of Control(IoC )containerインスタンスを解決するため。

しかし、これをC++でどのように行うのですか?これについて少し読みましたが、C++での依存性注入はC#ほど大きなトピックではないようです。 C++では、オブジェクトへの参照を使用します-これはC++でDIを使用する方法ですよね?

参照に関する私の理論が正しい場合、すべての参照を解決できるコンテナのようなものがありますか? C#では"bad class/bad project/Assembly"プログラムの開始時にすべてのインスタンスを静的コンテナに登録します。次に、すべてのクラスで、静的コンテナをインスタンス化して特定のインスタンスを解決できますが、これはC++で可能ですか?

C++でDependency Injection(またはそれと呼ばれるもの)を使用していますか?はいの場合、どのように使用していますか?C#に類似点はありますか?

22
Marcel Hoffmann

このためには、インターフェイスと、インスタンスを解決するためのコンテナが必要です。しかし、これをC++でどのように行うのでしょうか?

同じやり方で。違いは、C#で「インターフェイスにプログラミング」するのに対して、C++で「基本クラスにプログラミング」することです。さらに、C#にはないC++の追加ツールがあります(たとえば、ポリシーベースのテンプレートは、コンパイル時に選択された依存性注入を実装します)。

C++では、オブジェクトへの参照を使用しています。これは、C++でDIを使用する方法です。

番号;これはthe DIの使用方法ではありません。これはa C++でDIの使用方法です。

以下も考慮してください。

  • オブジェクトへのポインター(または、場合によってはスマートポインター)を使用する
  • ポリシーにテンプレート引数を使用します(例については、スマートポインターでのstd :: default_deleteの使用を参照)
  • 注入されたファンクター/述語でラムダ計算を使用します。

C#には、プログラムの起動時にすべてのインスタンスを静的コンテナに登録する「悪いクラス/悪いプロジェクト/アセンブリ」があります。

正しく理解できれば、すべてのデータをこの静的コンテナに設定し、アプリケーション全体で使用します。この場合、デメテルの法則に違反するため、依存性注入を正しく使用しません。

これはC++で可能ですか?

はい、それは完全に可能です(しかし、デメテルの法則に違反しているため、そうすべきではありません)。 boost :: anyをご覧ください(これにより、C#でobject参照によりオブジェクトを保存するのと同様に、異種オブジェクトをコンテナに保存できます)。

依存性注入またはC++で呼び出されるものを使用していますか?

はい(そしてそれは依存性注入と呼ばれます:))。

はいの場合、どのように使用していますか?

上記で説明したように(ポリシーテンプレート引数、再利用可能なコンポーネントとして挿入されたファンクターおよび述部、参照、ポインタースマートポインターまたは値によるオブジェクトの挿入)。

25
utnapistim

C++では、依存性注入の使用は非常に簡単です。依存関係を注入するクラスのコンストラクターまたは初期化関数への参照またはポインター(またはスマートポインター)引数として使用するインターフェイス(純粋な抽象基本クラス)を定義するだけです。

次に、単体テストでモックオブジェクト(抽象インターフェイスクラスから継承するクラスのインスタンス)を注入し、実際のコードで実際のクラスのインスタンス(同じインターフェイスクラスから継承する)を注入します。

簡単です。

16
Erik Alapää

プロジェクトの制限としてC++ 11を使用して、最終的に自分でロールバックしました。 Reflection ofcourseのない.NET Ninject APIに大まかに基づいて作成しました。

ServiceLocator

注:ラムダ関数バインディングを使用し、できればServiceLocator :: Moduleクラスを使用すると、リフレクションベースではなくインジェクションを取得し、本当にうまく機能します(IMO)

#include <iostream>
#include <vector>
#include "ServiceLocator.hpp"

template <class T>
using sptr = std::shared_ptr<T>;

// Some plain interfaces
class IFood {
public:
    virtual std::string name() = 0;
};

class IAnimal {
public:
    virtual void eatFavouriteFood() = 0;
};


// Concrete classes which implement our interfaces, these 2 have no dependancies
class Banana : public IFood {
public:
    std::string name() override {
        return "Banana";
    }
};

class Pizza : public IFood {
public:
    std::string name() override {
        return "Pizza";
    }
};

// Monkey requires a favourite food, note it is not dependant on ServiceLocator
class Monkey : public IAnimal {
private:
    sptr<IFood> _food;

public:
    Monkey(sptr<IFood> food) : _food(food) {
    }

    void eatFavouriteFood() override {
        std::cout << "Monkey eats " << _food->name() << "\n";
    }
};

// Human requires a favourite food, note it is not dependant on ServiceLocator
class Human : public IAnimal {
private:
    sptr<IFood> _food;

public:
    Human(sptr<IFood> food) : _food(food) {
    }

    void eatFavouriteFood() override {
        std::cout << "Human eats " << _food->name() << "\n";
    }
};

/* The SLModule classes are ServiceLocator aware, and they are also intimate with the concrete classes they bind to
   and so know what dependancies are required to create instances */
class FoodSLModule : public ServiceLocator::Module {
public:
    void load() override {
        bind<IFood>("Monkey").to<Banana>([] (SLContext_sptr slc) { 
            return new Banana();
        });
        bind<IFood>("Human").to<Pizza>([] (SLContext_sptr slc) { 
            return new Pizza();
        });
    }
};

class AnimalsSLModule : public ServiceLocator::Module {
public:
    void load() override {
        bind<IAnimal>("Human").to<Human>([] (SLContext_sptr slc) { 
            return new Human(slc->resolve<IFood>("Human"));
        });
        bind<IAnimal>("Monkey").to<Monkey>([] (SLContext_sptr slc) { 
            return new Monkey(slc->resolve<IFood>("Monkey"));
        });
    }
};

int main(int argc, const char * argv[]) {
    auto sl = ServiceLocator::create();

    sl->modules()
        .add<FoodSLModule>()
        .add<AnimalsSLModule>();

    auto slc = sl->getContext();

    std::vector<sptr<IAnimal>> animals;
    slc->resolveAll<IAnimal>(&animals);

    for(auto animal : animals) {
        animal->eatFavouriteFood();
    }

    return 0;
}
8
steve

はい、依存性注入はC++でも同様に便利です。特定の言語や構文を必要とせず、オブジェクト指向のクラスアーキテクチャを必要とするだけなので、そうすべきではない理由はありません(少なくともこれはおそらく最も一般的なケースです)。

C#には動的に割り当てられたオブジェクトへの「ポインター」しかありませんが、C++には「通常の」ローカル変数、複数の種類のポインター、参照などの複数のバリアントがあります。さらに、移動セマンティクスの概念はこれに非常に関連します。

C++では、オブジェクトへの参照を使用しています。これは、C++でDIを使用する方法です。

だけではありません。クラスメソッドに何かを渡すことができ、クラスオブジェクトが行う限りこの何かが存在する限り、何でも使用できます。上記の3つの可能性のすべてがそれを行うことができます(それぞれに特定の制限があります)

この参照をすべて解決できるコンテナのようなものはありますか? C#では、すべてのインスタンスを静的コンテナに登録する「悪いクラス/悪いプロジェクト/アセンブリ」を持っています

たぶん、あなたは依存注射のポイントを逃しているのでしょう。 「グローバル」変数の束とは異なります。ただし、もちろん、これはC++でも可能です。クラスがあり、staticがあり、それがすべて必要です。

4
deviantfan

参照に関する私の理論が正しい場合、すべての参照を解決できるコンテナのようなものがありますか? C#には、プログラムの起動時にすべてのインスタンスを静的コンテナに登録する「不良クラス/不良プロジェクト/アセンブリ」があります。次に、すべてのクラスで、静的コンテナをインスタンス化して特定のインスタンスを解決できますが、これはC++で可能ですか?

これは、DIの使用方法ではありません。コンテナをすべての「消費者」クラスに渡すわけではありません。適切に設計されたアプリケーションでは、エントリポイントで解決することはほとんどなく、それだけです。ほとんどの場合、「解決」の必要性は、登録されて注入されるファクトリを使用することで置き換えることができます。

静的クラスによっては、コードのテストで多くの問題が発生します。少なくともインスタンスにコンテナをクライアントクラスにインジェクトしてインジェクトすることをお勧めします。静的な依存関係は地獄であり、ユニットテスト用にモックする方が簡単です。

1
Emmanuel Istace