web-dev-qa-db-ja.com

依存性注入がオブジェクト指向プログラミングを強制終了しないようにする方法

最近、多くの人々がプロジェクトでDIを採用しました(私はaspnetコアで作業しています)。私が抱えている問題は、DIが私のコードを手続き型パラダイムに向けることです。たとえば、OOPのように:

class Something
{
    Something(Other object, Dependency dep); // created manually or by factory obj

    DoStuff();
}

ただし、DIでは依存関係を満たす必要があるため、Somethingクラスを直接作成することはできません。今私のコードは一般的にそのように見えます:

class Something
{
    Something(Dependency dep); // resolved from di container

    DoStuff(Object obj); // executed manually
}

結果として、私のオブジェクトにはメソッドがなく、さまざまなサービスによって処理されるプロパティのバッグにすぎません。一方、単純なタスクを実行するためだけに、依存関係の多い巨大なオブジェクトを作成する必要があります。これには、作成されたオブジェクトグラフの1%が必要です。

デザインを修正するには?このアプローチには明らかな欠点があります。たとえば、Somethingクラスを渡すことができないのは、ObjectからDoStuffが必要なため、他のクラスにパックする必要があるためです。他の多くの問題があります。たとえば、機能が多くのファイルに散在している、セキュリティの実装が困難である(DIから「内部」サービスを要求できるため)、またはカプセル化(オブジェクトは外部クラスによって操作されるため、すべてのプロパティをパブリックにする必要があります)。


編集:より良い説明のための小さな例:

私がDIの前に書くApi:

class ShoppingCartFactory {
    ShoppingCart Create(id);
}

// encapsulates cart logic
class ShoppingCart {
    // internal dependencies passed to cart from factory
    ShoppingCart(DeliveryLoader, ItemMetadataLaoder);


    // methods encapsulation functionality
    Modify(item, quantity);
    Save();
    Empty();
    GetDeliveryOptions(); // loads possible transports from db
    LoadItemMetadata(); // loads prices, availability etc. for items

    // public properties
    ImmutableList<CartItem> items; // immutable list of items
    Delivery selectedDelivery; // transport selected by client
}

クライアントがショッピングカートをクリックすると、配信オプションが1回だけ表示され、その後のリクエストでは必要ありません。したがって、このアプローチでは、リクエストごとにShoppingCartDeliveryCreator依存関係を作成する必要があります。

代わりに、私はそのようなものを書かなければなりません:

// instead of having properties on cart Object now I have 3 separate classes
// in previous example user of api had no idea how cart works inside
// now he needs to find all classes associated with cart
// also service class needs to take id in each method
// or needs some method like `Contextualize(id)`
class ShoppingCartService {
    // executes logic on db
    ShoppingCart Read(id);
    ShoppingCart Modify(id, item, quenatity);
    ShoppingCart Empty(id);
    ShoppingCart Save(id, shoppingCart);
}
class ShoppingCartDeliveryCreator (...)
class ShoppingCartItemMetadataLoader (...)

// no logic
class ShoppingCart {
    List<CartItem> items; // cannot be immutable, because Service needs to manipulate it
    Delivery selectedDelivery; // same
}

ShoppingCartクラスでは何もできません。常にサービスの1つが必要です。デレク・エルキンスが指摘したように

Dependency Injectionを体系的に使用した結果の1つは、クラス階層が完全にフラットになることです。

これが良いことかどうかはわかりません。私は多くの研究と実験を行いましたが、私はこのスタイルのプログラミングに確信が持てません。何かがどのように機能するかについてのアイデアを得るにはそのすべてのコードを調べて、それが何をしているのかを推測する必要があります。古い例では、クラスを見るだけですべてのオプションが表示されるか、ShoppingCartを受け取るすべてのクラスをチェックできました。

4
Shadow

Doc Brownが述べたように、Dependency Injectionコンテナーの使用はDependency Injectionではありません。 Dependency Injectionは単なるパラメーター化であり、Dependency Injectionコンテナーは配線依存関係を「単純化」することのみを目的としています。あなたの問題は主に、使用しているDIコンテナーフレームワークの(認識された)制限によって引き起こされているようです。

あなたのオブジェクトが「プロパティのバッグ」であることがどのように依存性注入の「結果」であるのか理解できません。一部のオブジェクトareは単に「プロパティのバッグ」になるため、依存関係はありません。これらは、「Plain Old Java/C#オブジェクト」の「POJO/POCO」オブジェクトと呼ばれます。当然、依存関係を必要としないので、これをallオブジェクトにすることはできません。

「作成されたオブジェクトグラフの1%しか必要としない」「多くの依存関係を持つ巨大なオブジェクト」にも言及しています。これは少し矛盾しています。 「作成されたオブジェクトグラフ」の1%しか必要ない場合、なぜそれが残りの99%に依存しているのですか?依存関係に必要なもの以外の依存関係が必要な場合、これは依存関係が十分に因数分解されていないことを示しています。 OOD用語では、単一責任原則の違反。 Dependency Injectionを体系的に使用した結果の1つは、クラス階層が完全にフラットになることです。必要な「階層」は、物事がどのように相互に関連付けられているかによって決まります(厳密に階層である必要はありません)。各クラスは、比較的狭いインターフェースを介して、必要なものだけに依存する必要があります。各クラスが1つの「サービス」のみを提供する場合、依存関係が必要以上のオブジェクトグラフをインスタンス化することはありません。クラスが複数の「サービス」を提供する十分な理由がたくさんあります。これは、「サービス」の下流のコンシューマーにとって無関係な依存関係につながる可能性がありますが、これは非常に明白であり、おそらく何らかの明示的な理由で行われます。そうでない場合は、クラスを複数のクラスに分解し、それぞれが「サービス」の1つだけを提供するようにします。 (「サービス」は通常、クラスが実装するインターフェースにかなり対応しています。したがって、複数のインターフェースを実装するクラスがある場合、これはクラスが複数の「サービス」を提供していることを示しています。)

とにかく、あなたが提供したコードの限られた例については、かなり明確な解決策があります。まず、明示的に使用しているDIコンテナーフレームワークがこれを提供している可能性は十分ありますが、そうではないと仮定しましょう。 Somethingの消費者は実際に何を必要としますか? Somethingsが与えられたときにOthersを与える何かが必要です。 (より正確には、Somethingsが提供する「サービス」を表すインターフェイスのインスタンスを提供するものです。実際にSomethingsであるかどうかは気にする必要はありません。)つまり、SomethingFactoryが必要です。したがって、Somethingsを必要とするクラスはSomethingFactoryに依存し、それを使用してSomethingsを作成する必要があります。コードで:

_class Something : ISomething {
    public Something(Other object, Dependencies dependencies) {...}
    // ...
}

interface ISomethingFactory {
    ISomething CreateSomething(Other object);
}

class SomethingFactory : ISomethingFactory {
    private readonly Dependencies _dependencies;
    public SomethingFactory(Dependencies dependencies) {
        _dependencies = dependencies;
    }
    public ISomething CreateSomething(Other object) {
        return new Something(object, _dependencies);
    }
}

class SomethingConsumer {
    public SomethingConsumer(ISomethingFactory somethingFactory) {...}
}
_

このアプローチでは、new Something(object)を直接記述するのとほぼ同じエクスペリエンスに加えて、無関係な依存関係を心配する必要がありません。あなたが(または一般的に)説明した理由から、依存性注入がすべてのプロパティをパブリックにするか、「手続き型スタイル」などを使用する必要が生じる理由はありません。 OO "自然に"書かれたプログラムを取り、依存性注入を使用してそれを機械的に1つに変換できます。 "自然に"書いたものを単に "OO"スタイルで記述し、 newは、上記で示した依存関係またはファクトリーのいずれかで渡されます。いくつかの追加のパラメーターを渡す必要があることを除いて、コードの構造は、以前とほぼ同じです。必要に応じて、これよりもはるかに広範囲の変更を行うには、何かを誤解していることになります(これはsuggestより広範囲の変更になる可能性がありますが、必要ではありません)。

注意として、特定のDIコンテナーを使用しているという事実は、コードの大部分から明らかですnotnotは、DIコンテナーを渡し、それを使用してオブジェクトをインスタンス化する必要があります。 DIコンテナーは、すべてを一緒に配線するトップレベルコードを簡略化するためにのみ使用します(使用する場合)。 「DIから「インナー」サービスをリクエストできる」という意味がわかりませんが、このようなことをしているようです。

13

私が抱えている問題は、DIが私のコードを手続き型パラダイムに向けることです。

あなたの例は「手続き的アプローチ」を示していません。


結果として、私のオブジェクトにはメソッドがなく、さまざまなサービスによって処理されるプロパティのバッグにすぎません。一方、単純なタスクを実行するためだけに、依存関係の多い巨大なオブジェクトを作成する必要があります。これには、作成されたオブジェクトグラフの1%が必要です。

デザインを修正するには?

多分単一層の抽象化パターンがここで役立ちます:

通常、SLAパターンをメソッドに適用します。つまり、メソッドは実際の計算を行うか、オブジェクトの状態を変更するか、(依存関係または同じクラスで)他のメソッドを呼び出します。

ただし、そのパターンをクラスにも拡張できます。

クラスを「ワーカー」と「ディスパッチャー」に分割し、注入する依存関係はないが、パラメーターやメンバー(「ワーカー」、および依存関係はあるが他のクラスを実行する他のクラス)の計算を行うようにする何も計算せず、その依存関係のメソッドを呼び出すだけです(分岐およびループ許可-「ディスパッチャー」)

0
Timothy Truckle

DIとDIコンテナーフレームワークが混乱しています。

もちろん、オブジェクトを手動で作成することもできます。必要な依存関係を渡すだけです。コンテナから常に自動入力する必要はありません。

public class myController
{
    IDependency dep;
    public myController(IDependency dep) // injected by di container
    {
        this.dep = dep;
    }
    public void Whatever()
    {
        var obj = new Object();
        var s = new Something(obj, dep); // injected manually
        s.DoSomething();
    }
}
0
Ewan