合成と依存関係の注入を使用したいのですが、各エンティティにいくつかの依存性が注入され始めると、特定の積み上げが得られます。
プロジェクトは成長しており、オブジェクトにますます多くの依存関係を注入し、メソッドを何度もリファクタリングする必要があります。
しかし、より管理しやすい方法があります。
この記事は、DIの使用を開始したばかりのユーザー、またはまったく使用していないユーザーで、IoCに特に慣れていないユーザーに役立ちます。 このアプローチは他の言語にも適用されますが、作成者がSwiftで記述しているため、すべての例がSwiftにあります(約Per。)
問題
突然ImageProvider
プロバイダーを必要とするオブジェクトがある場合、次のように記述します。
class FooCoordinator { let imageProvider: ImageProvider init(..., imageProvider: ImageProvider) ///... }
非常にシンプルで便利+これにより、テストでプロバイダーを置き換えることができます。
コードベースが大きくなるにつれて、依存性がますます現れ、それぞれが以下を強制します。
- 使用場所をリファクタリングする
- 新しい変数を追加する
- 定型文を書く
たとえば、数か月後、オブジェクトには3つの依存関係がある場合があります。
class FooCoordinator { let imageProvider: ImageProvider let articleProvider: ArticleProvider let persistanceProvider: PersistanceProvider init(..., imageProvider: ImageProvider, articleProvider: ArticleProvider, persistanceProvider: PersistanceProvider) { self.imageProvider = imageProvider self.articleProvider = articleProvider self.persistanceProvider = persistanceProvider ///... } ///... }
通常、プロジェクトには複数のクラスがあるため、同じパターンが何度も繰り返されます。
そして、たとえばAppController
やFlow Coordinator
など、これらすべての依存関係へのリンクを保存する場所が必要であることを忘れてはなりません。
このアプローチは必然的に痛みにつながります。 痛みは、シングルトンなど、あまり適切でない決定の使用を動機付けることができます。
しかし、コードインジェクションの利点をすべて備えた、シンプルで簡単なコードサポートが必要です。
代替案
プロトコルの構成(インターフェイス)を使用して、コードの可読性とサービス品質を向上させることができます。
依存関係のベースプロトコルコンテナについて説明しましょう。
protocol Has{Dependency} { var {dependency}: {Dependency} { get } }
{Dependency}をオブジェクトの名前に変更します
たとえば、 ImageProvider
ようになります。
protocol HasImageProvider { var imageProvider: ImageProvider { get } }
Swiftでは、 &
演算子を使用してプロトコルからコンポジションを作成できます。つまり、エンティティに依存関係のリポジトリを1つだけ含めることができます。
class FooCoordinator { typealias Dependencies = HasImageProvider & HasArticleProvider let dependencies: Dependencies init(..., dependencies: Dependencies) }
AppController
またはFlow Coordinator
(またはエンティティの作成に使用されるもの)で、1つのコンテナの下にあるすべての依存関係を構造の形で非表示にできます。
struct AppDependency: HasImageProvider, HasArticleProvider, HasPersistanceProvider { let imageProvider: ImageProvider let articleProvider: ArticlesProvider let persistanceProvider: PersistenceProvider }
そして、すべてのアプリケーションの依存関係は、ロジックや魔法のようなものではなく、単なる構造を持つコンテナに保存されます。
このアプローチにより、読みやすさが向上し、すべての依存関係が一緒に保存されますが、さらに重要なことは、オブジェクトに必要な依存関係に関係なく、構成コードは常に同じであるということです。
class FlowCoordinator { let dependencies: AppDependency func configureViewController(vc: ViewController) { vc.dependencies = dependencies } }
各オブジェクトは、実際に必要な依存関係のみを記述し、それらのみを受け取ります。
たとえば、 FooCoordinator
がImageProvider
必要とする場合ImageProvider
AppDependency
構造をImageProvider
Swiftの型チェックはImageProvider
へのアクセスのみを提供します
将来的に、たとえばPersistanceProvider
ような依存関係がさらに必要になった場合、 typealias
に追加するだけです。
class FooCoordinator { typealias Dependencies = HasImageProvider & HasArticleProvider & HasPersistanceProvider }
それだけです
このアプローチにはいくつかの利点があります。
- 依存関係は明確に定義されており、プロジェクト全体を通じてあらゆるオブジェクトで常に一貫しています
- オブジェクトの依存関係が変更された場合、変更する必要があるのは
typealias
のみtypealias
- 初期化子も構成関数も変更する必要はありません。
- Swift型チェックシステムのおかげで、どのオブジェクトも必要な依存関係のみを取得します。