依存性注入を使用する場合、1つのクラスで許容できる注入の数


9

依存関係の注入にUnityをC#で使用していますが、この質問は、依存関係の注入を使用しているすべての言語とフレームワークに適用できます。

私はSOLID原理に従うようにしているので、多くの抽象化を得ました。しかし今、私は、1つのクラスが何回の注入を注入すべきかについてのベストプラクティスがあるかどうか疑問に思っていますか?

たとえば、9つの注入があるリポジトリがあります。これは他の開発者にとって読みにくいでしょうか?

注射には次の責任があります。

  • IDbContextFactory-データベースのコンテキストを作成します。
  • IMapper-エンティティからドメインモデルへのマッピング。
  • IClock-DateTime.Nowを抽象化して、単体テストを支援します。
  • IPerformanceFactory-特定のメソッドの実行時間を測定します。
  • ILog-ロギング用のLog4net。
  • ICollectionWrapperFactory-コレクションを作成します(IEnumerableを拡張します)。
  • IQueryFilterFactory-dbをクエリする入力に基づいてクエリを生成します。
  • IIdentityHelper-ログインしたユーザーを取得します。
  • IFaultFactory-異なるFaultExceptionsを作成します(私はWCFを使用しています)。

私が責任を委任した方法に本当にがっかりしているわけではありませんが、読みやすさが気になり始めています。

だから、私の質問:

クラスに必要な注射の回数に制限はありますか?もしそうなら、それを避ける方法は?

多くの注入は読みやすさを制限しますか、それとも実際にそれを改善しますか?


2
数値で品質を測定することは、通常、1か月に記述するコード行によって支払われるのと同じくらい良くありません。ただし、依存関係の実際の例を含めましたが、これは優れたアイデアです。私があなただったら、あなたの質問を再定式化して、数え上げと厳格な制限の概念を取り除き、品質そのものの代わりに焦点を合わせるようにします。再定式化する際は、質問を具体的にしすぎないように注意してください。
Arseni Mourzenko 2016年

3
コンストラクタ引数はいくつ受け入れられますか?IoC はそれを変更しません
Telastyn 2016年

なぜ人々は常に絶対的な限界を望んでいるのですか?
Marjan Venema 2016年

1
@Telastyn:必ずしもそうではありません。IoCの変更点は、静的クラス/シングルトン/グローバル変数に依存するのではなく、すべての依存関係がより集中化され、より「可視」になることです。
Arseni Mourzenko 2016年

@MarjanVenema:生活がずっと楽になるから。メソッドごとの最大LOC、クラス内のメソッドの最大数、またはメソッドの変数の最大数が正確にわかっていて、これが重要な唯一の事項である場合、コードを以下のように良いか悪いかで簡単に修飾できます。悪いコードを「修正」するだけでなく。これは残念なことに、実際の生活はそれよりもはるかに複雑であり、多くの指標はほとんど関係ありません。
Arseni Mourzenko 2016年

回答:


11

依存関係が多すぎる場合は、クラス自体の処理が多すぎる可能性があります。あまり実行していないかどうかを確認するには:

  • クラス自体を見てください。それを2つ、3つ、4つに分割することは理にかなっていますか?全体として意味がありますか?

  • 依存関係のタイプを見てください。どれがドメイン固有で、どれが「グローバル」ですか?たとえば、私はILog同じレベルでは考慮しませんIQueryFilterFactory。ロギングを使用している場合、最初のクラスはほとんどのビジネスクラスで使用できます。一方、ドメイン固有の依存関係が多数見つかった場合は、クラスの処理が多すぎる可能性があります。

  • 値で置き換えることができる依存関係を見てください。

    IClock-DateTime.Nowを抽象化して、単体テストを支援します。

    これはDateTime.Now、現在時刻を知る必要があるメソッドに直接渡されることで簡単に置き換えることができます。

実際の依存関係を見ると、悪いことが起こっていることを示すものは何も見当たりません。

  • IDbContextFactory-データベースのコンテキストを作成します。

    OK、クラスがデータアクセスレイヤーとやり取りするビジネスレイヤーの内部にいる可能性があります。元気そうです。

  • IMapper-エンティティからドメインモデルへのマッピング。

    全体像がなければ何も語ることは難しい。アーキテクチャが間違っている可能性があり、マッピングはデータアクセス層によって直接行われる必要があるか、またはアーキテクチャが完全に正常である可能性があります。すべての場合において、ここでこの依存関係を持つことは理にかなっています。

    もう1つの選択肢は、クラスを2つに分割することです。1つはマッピングを処理し、もう1つは実際のビジネスロジックを処理します。これは、BLをDALからさらに分離するデファクトレイヤーを作成します。マッピングが複雑な場合、それは良い考えです。しかし、ほとんどの場合、それは役に立たない複雑さを追加するだけです。

  • IClock-DateTime.Nowを抽象化して、単体テストを支援します。

    現在の時刻を取得するためだけに別のインターフェース(およびクラス)を用意することは、おそらくあまり役​​に立ちません。DateTime.Now現在の時間を必要とするメソッドに単に渡します。

    タイムゾーンや日付範囲などの他の情報がある場合は、別のクラスが意味をなす場合があります。

  • IPerformanceFactory-特定のメソッドの実行時間を測定します。

    次のポイントを参照してください。

  • ILog-ロギング用のLog4net。

    このような超越的な機能はフレームワークに属している必要があり、実際のライブラリは実行時に交換可能で構成可能である必要があります(たとえば、.NETのapp.configを介して)。

    残念ながら、これは(まだ)事実ではありません。ライブラリを選択してそれに固執するか、必要に応じて後でライブラリを交換できるように抽象化レイヤーを作成できます。ライブラリの選択から独立することを特に意図している場合は、それを選択してください。ライブラリを何年も使用し続けると確信している場合は、抽象化を追加しないでください。

    ライブラリが複雑すぎて使用できない場合は、ファサードパターンが理にかなっています。

  • ICollectionWrapperFactory-コレクションを作成します(IEnumerableを拡張します)。

    これにより、ドメインロジックで使用される非常に具体的なデータ構造が作成されると思います。ユーティリティクラスのように見えます。代わりに、関連するコンストラクターを使用して、データ構造ごとに1つのクラスを使用します。初期化ロジックがコンストラクターに収まるように少し複雑である場合は、静的ファクトリーメソッドを使用します。ロジックがさらに複雑な場合は、ファクトリーまたはビルダーパターンを使用します。

  • IQueryFilterFactory-dbをクエリする入力に基づいてクエリを生成します。

    なぜそれがデータアクセス層にないのですか?Filter名前にaがあるのはなぜですか?

  • IIdentityHelper-ログインしたユーザーを取得します。

    なぜHelperサフィックスがあるのか​​わかりません。すべての場合において、他のサフィックスも特に明示的ではありません(IIdentityManager?)

    とにかく、ここにこの依存関係があることは完全に理にかなっています。

  • IFaultFactory-異なるFaultExceptionsを作成します(私はWCFを使用しています)。

    ロジックが非常に複雑なので、ファクトリパターンを使用する必要がありますか?なぜDependency Injectionを使用するのですか?プロダクションコードとテストの間で例外の作成を交換しますか?どうして?

    それを単純なものにリファクタリングしてみthrow new FaultException(...)ます。すべての例外をグローバルな情報に追加してからクライアントに伝達する必要がある場合、WCFには、未処理の例外をキャッチして変更し、クライアントに再スローできるメカニズムがある可能性があります。

クラスに必要な注射の回数に制限はありますか?もしそうなら、それを避ける方法は?

数値で品質を測定することは、通常、1か月に記述するコード行によって支払われるのと同じくらい悪いです。少数の依存関係を使用してくだらないクラスを作成できるため、適切に設計されたクラスには多数の依存関係が存在する可能性があります。

多くの注入は読みやすさを制限しますか、それとも実際にそれを改善しますか?

依存関係が多いため、ロジックをたどるのが難しくなります。ロジックを理解するのが難しい場合は、クラスが多すぎる可能性があるため、分割する必要があります。


コメントと時間をありがとうございました。主に、代わりにWCFロジックに移動されるFaultFactoryについてです。IClockは、アプリケーションをTDDするときの命の恩人であるため、そのままにしておきます。特定の時間に特定の値が設定されていることを確認したい場合がいくつかあります。その場合、DateTime.Nowはモック可能ではないため、常に十分であるとは限りません。
smoksnes 2016年

7

これは、DIの典型的な例であり、クラスがおそらく1つのクラスでは大きくなりすぎていることを示しています。これは、「わあ、DIはコンストラクターを木星よりも大きくするので、この手法は恐ろしい」と解釈されがちですが、実際には、「クラスには依存関係が大量にある」ということがわかります。これを知って、私たちはどちらかをすることができます

  • 代わりに新しい依存関係を開始することで、ラグの下で問題を一掃します
  • デザインを見直してください。おそらくいくつかの依存関係は常に一緒になり、他のいくつかの抽象化の背後に隠されるべきです。多分あなたのクラスは2に分割する必要があります。多分それはいくつかの依存関係の小さなサブセットを必要とするいくつかのクラスで構成される必要があります。

依存関係を管理する方法は無数にあり、コードとアプリケーションを知らなければ、ケースに最適な方法を説明することは不可能です。

最後の質問に答えるには:

  • クラスに必要な依存関係の数に上限はありますか?

はい、上限は「多すぎ」です。「多すぎる」はいくつですか?「多すぎる」とは、クラスの結束力が「低すぎる」ときです。それはすべて異なります。通常、クラスに対する反応が「すごい、これには多くの依存関係があります」である場合、それは多すぎます。

  • 依存関係を注入すると読みやすさが向上または損なわれますか?

この質問は誤解を招くと思います。答えは「はい」でも「いいえ」でもかまいません。しかし、それは最も興味深い部分ではありません。依存関係を挿入するポイントは、依存関係を可視にすることです。それは嘘をつかないAPIを作ることです。グローバルな状態を防ぐことです。コードをテスト可能にすることです。カップリングを減らすことです。

適切に設計され名前が付けられたメソッドを備えた適切に設計されたクラスは、不適切に設計されたクラスよりも読みやすくなっています。DI自体は読みやすさ自体を実際に改善または損なうものではありません。デザインの選択を目立たせるだけであり、DIが悪いと目を痛めます。これは、DIがコードの可読性を低下させたことを意味するのではなく、コードがすでに混乱していることを示し、非表示にしただけです。


1
良いフィードバック。ありがとうございました。はい、上限は「多すぎ」です。-幻想的で、そうです。
smoksnes 2016年

3

ユビキタス「コードコンプリート」の作者であるスティーブマッコネルによると、経験則では、7以上はコードのにおいであり、保守性を損ないます。私は個人的にはほとんどの場合この数は少ないと思いますが、DIを適切に実行すると、コンポジションルートに非常に近い場合に注入する依存関係が多くなります。これは正常であり、予想されたものです。これは、IoCコンテナーが有用であり、プロジェクトに追加する複雑さの価値がある理由の1つです。

したがって、プログラムのエントリポイントに非常に近い場合、これは正常で許容範囲です。あなたがプログラムのロジックをより深く理解しているなら、それはおそらく対処されるべき臭いです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.