1. ホーム
  2. c#

[解決済み】Dependency Inject (DI)の "フレンドリー "なライブラリ

2022-05-09 03:31:03

質問

C#のライブラリの設計について考えているのですが、そのライブラリにはいくつかの異なるハイレベルな関数があります。もちろん、これらの高レベルの関数は、C#のライブラリで実装されます。 ソリッド というクラス設計の原則があります。そのため、おそらく消費者が定期的に直接使用することを目的としたクラスや、より一般的な「エンドユーザー」クラスの依存関係にある「サポート」クラスが存在することでしょう。

問題は、そうなるようにライブラリを設計する最善の方法は何かということです。

  • DIアグノスティック - 一般的なDIライブラリ(StructureMap、Ninjectなど)の1つか2つに基本的なサポート"を追加することは合理的だと思いますが、コンシューマはどのDIフレームワークでもライブラリを使用できるようにしたいのです。
  • DIを使用しない場合 - ライブラリの消費者がDIを使用しない場合でも、ライブラリはできるだけ使いやすく、ユーザーが使用したい本当のクラスに到達するために必要な、重要でない依存関係をすべて作成しなければならない作業量を減らす必要があります。

現在考えているのは、一般的なDIライブラリ(StructureMapレジストリやNinjectモジュールなど)用のいくつかのquot;DI登録モジュールと、非DIでこれらのいくつかのファクトリへのカップリングを含む一連のファクトリクラスを提供することです。

いかがでしょうか?

解決方法は?

これは、DIが以下のようなものであることを理解すれば、実は簡単なことなのです。 パターンと原則 技術ではありません。

DI Containerに依存しない方法でAPIを設計するには、以下の一般原則に従います。

実装ではなく、インターフェイスにプログラムする

この原則は、実は、(記憶ではあるが)以下の引用である。 デザインパターン しかし、それは常にあなたの 真の目標 . DIはその目的を達成するための手段に過ぎない .

ハリウッドの原則を適用する

ハリウッドプリンシプルをDIで言うと DIコンテナを呼ぶな。 .

コード内からコンテナを呼び出して、依存関係を直接要求してはいけません。暗黙的に依存関係を求めるには コンストラクタ・インジェクション .

コンストラクタ・インジェクションの使用

依存関係が必要なとき、それを求める 静的 をコンストラクタで実行します。

public class Service : IService
{
    private readonly ISomeDependency dep;

    public Service(ISomeDependency dep)
    {
        if (dep == null)
        {
            throw new ArgumentNullException("dep");
        }

        this.dep = dep;
    }

    public ISomeDependency Dependency
    {
        get { return this.dep; }
    }
}

Serviceクラスがどのように不変性を保証しているかに注目してください。インスタンスが作成されると、Guard Clause と readonly というキーワードがあります。

短命のオブジェクトが必要な場合は、抽象ファクトリーを使用します。

コンストラクタ注入で注入される依存関係は長寿命になる傾向がありますが、時には短命のオブジェクトが必要になったり、実行時にしか分からない値に基づいて依存関係を構築したりすることがあります。

参照 これ をご覧ください。

最後の責任ある瞬間にのみ作曲する

最後の最後までオブジェクトを非連結にする。通常、アプリケーションのエントリポイントでは、すべてを待って配線することができます。これを コンポジションルート .

詳しくはこちら

Facadeを使った簡略化

もし、出来上がったAPIが初心者のユーザーにとって複雑すぎると感じたら、いつでも、いくつかの ファサード クラスは、一般的な依存関係の組み合わせをカプセル化したものです。

柔軟で発見力の高いFacadeを提供するために、Fluent Buildersを用意することも考えられる。こんな感じ。

public class MyFacade
{
    private IMyDependency dep;

    public MyFacade()
    {
        this.dep = new DefaultDependency();
    }

    public MyFacade WithDependency(IMyDependency dependency)
    {
        this.dep = dependency;
        return this;
    }

    public Foo CreateFoo()
    {
        return new Foo(this.dep);
    }
}

これは、ユーザーがデフォルトのFooを作成するために、次のように記述することを可能にします。

var foo = new MyFacade().CreateFoo();

しかし、カスタムの依存関係を供給することが可能であることは非常に発見的であり、次のように書くことができるでしょう。

var foo = new MyFacade().WithDependency(new CustomDependency()).CreateFoo();

MyFacadeクラスが多くの異なる依存関係をカプセル化すると想像すると、拡張性を発見できるようにしながら、適切なデフォルトを提供する方法が明らかになるかと思います。


参考までに、この回答を書いたずっと後に、ここに書かれているコンセプトを発展させ、次のような長いブログ記事を書きました。 DIフレンドリーなライブラリ についての記事と DIフレンドリーフレームワーク .