web-dev-qa-db-ja.com

依存関係注入のためのインターフェースが必要ですか?

ASP.NET Coreアプリケーションがあります。アプリケーションには、いくつかの作業を行うヘルパークラスがほとんどありません。各クラスには異なる署名メソッドがあります。各クラスのインターフェイスを作成し、タイプをDIフレームワークに登録する.netコアの例をオンラインで多数見かけます。例えば

 public interface IStorage
 {
    Task Download(string file);
 }

 public class Storage
 {
    public Task Download(string file)
    {
    }
 }

 public interface IOcr
 {
     Task Process();
 }

 public class Ocr:IOcr
 {
    public Task Process()
    {

    }
 }

基本的に、各インターフェースには1つのクラスしかありません。次に、これらのタイプをDIとして登録します

 services.AddScoped<IStorage, Storage>();
 services.AddScoped<IOcr,Ocr>();

しかし、インターフェースがなくてもタイプを登録できるので、ここのインターフェースは冗長に見えます。例えば

 services.AddScoped<Storage>();
 services.AddScoped<Ocr>();

本当にインターフェースが必要ですか?

25
LP13

いいえ、依存関係の注入にインターフェースは必要ありません。しかし、依存関係の注入は、それらを使用する方がはるかに便利です。

お気づきのとおり、具体的な型をサービスコレクションに登録すると、ASP.NET Coreが問題なくそれらをクラスに挿入します。 new Storage()を使用してインスタンスを作成するだけで得られる唯一のメリットは、 サービスの有効期間の管理 (一時的なものとスコープ付きのものか、シングルトンか)です。

これは便利ですが、DIを使用する力の一部にすぎません。 @DavidGが指摘したように、インターフェースがDIと頻繁にペアになっている大きな理由は、テストのためです。コンシューマークラスを他の具象クラスではなくインターフェイス(抽象化)に依存させると、テストがはるかに容易になります。

たとえば、テスト中に使用するためにMockStorageを実装するIStorageを作成すると、コンシューマクラスは違いを認識できません。または、モックフレームワークを使用して、モックしたIStorageをその場で簡単に作成できます。具象クラスで同じことをするのはずっと難しいです。インターフェイスを使用すると、抽象化を変更せずに実装を簡単に置き換えることができます。

25
Nate Barbettini

うまくいきますか?はい。あなたはそれをするべきですか?番号。

依存関係の注入は、依存関係の逆転の原則のためのツールです: https://en.wikipedia.org/wiki/Dependency_inversion_principle

または [〜#〜] solid [〜#〜] で説明されているように

「具体化ではなく、抽象化に依存する」べきです。

あなたはできる場所全体に具象クラスを注入するだけで動作しますしかし、それはDIが達成するために設計されたものではありません。

11
MindingData

他の人がすでに言及していることをカバーするつもりはありません。DIとのインターフェースを使用することは、多くの場合最良のオプションです。ただし、オブジェクトの継承を使用すると、別の便利なオプションが提供される場合があることに言及する価値があります。だから例えば:

public class Storage
 {
    public virtual Task Download(string file)
    {
    }
 }


public class DiskStorage: Storage
 {
    public override Task Download(string file)
    {
    }
 }

そのように登録します:

services.AddScoped<Storage, DiskStorage>();
2
Ron C

いいえ、インターフェースは必要ありません。クラスまたはインターフェースの注入に加えて、デリゲートを注入することもできます。これは、1つのメソッドでインターフェースを注入することに相当します。

例:

public delegate int DoMathFunction(int value1, int value2);

public class DependsOnMathFunction
{
    private readonly DoMathFunction _doMath;

    public DependsOnAFunction(DoMathFunction doMath)
    {
        _doMath = doMath;
    }

    public int DoSomethingWithNumbers(int number1, int number2)
    {
        return _doMath(number1, number2);
    }
}

デリゲートを宣言せずに、Func<Something, Whatever>そしてそれはまた働くでしょう。 DIを設定する方が簡単なので、デリゲートに寄りかかります。同じシグネチャを持つ2つのデリゲートがあり、それらが関連のない目的に役立つ場合があります。

これの1つの利点は、インターフェイスの分離に向けてコードを誘導することです。誰かがメソッドをインターフェース(とその実装)に追加したくなるかもしれません。なぜなら、それはすでにどこかに注入されているので便利だからです。

つまり

  • インターフェースと実装は、現時点で誰かにとって便利だからといって、彼らが持つべきではない責任を負います。
  • インターフェースに依存するクラスもその責任において成長する可能性がありますが、その依存関係の数が増加していないため、特定するのが困難です。
  • 他のクラスは、肥大化し、それほど分離されていないインターフェースに依存します。

何か新しいものを注入する代わりに既存のインターフェースとクラスに追加する方が便利だったので、単一の依存関係が最終的に実際には完全に2つまたは3つの完全に別のクラスになるはずのケースを見てきました。その結果、一部のクラスは2,500行の長さになりました。

誰かがしてはいけないことをするのを防ぐことはできません。クラスを10人の異なるデリゲートに依存させるだけでは止められません。しかし、将来の成長を正しい方向に導くパターンを設定することができ、インターフェイスとクラスの制御の拡大に対してある程度の抵抗を提供します。

(これはインターフェースを使用しないという意味ではありません。オプションがあることを意味します。)

0
Scott Hannen