2

グローバル状態を使用するのではなく、View Controller間で依存関係を渡す(注入する)ことの利点がわかります。しかし、私は人々が実際にこれをどのように実装するのか興味があります。

最も単純なケースでは、2つのViewController間で単一のモデルを渡すのは簡単です。

MyViewController *vc = [[MyViewController alloc] init];
vc.model = model;
[self.navigationController pushViewController:vc animated:YES];

ただし、このアプローチのスケーリングには2つの問題があります。

1)すべてのView Controller(ロケーションマネージャ、オブジェクトストアなど)に必要なサービスがいくつかある場合があります。したがって、ViewControllerごとに設定する必要のある依存関係のリストができあがります。

MyViewController *vc = [[MyViewController alloc] init];
vc.model = model;
vc.locationManager = locationManager;
vc.objectStore = objectStore;
...
[self.navigationController pushViewController:vc animated:YES];

2)2番目の問題は、最初の問題に関連しています。ViewControllerをプッシュする前に、これらの依存関係を設定することを実際に強制しているわけではありません。すべての依存関係を必要とするinitメソッドを作成できると思いますが、それでも強制することはできません。また、時間がかかり、後で依存関係を追加したい場合は、非常に苦痛になります。

これらの問題を処理するために使用されているアプローチは何ですか?多くのObj-Cの人々が依存性注入フレームワークを使用しているようには見えません。私が考えることができる1つの方法は、すべての共有依存関係を含むAppContextクラスを作成し、それをすべてのViewControllerに渡すことです。

また、一般に、(少なくともJavaでは)依存関係を実装ではなくインターフェースで宣言するので、単体テストのためにそれらをモックすることができます。このようにプロトコルを使用しているObj-Cの人々はあまり見かけません。次に、単体テストの依存関係をどのようにモックしますか?

4

2 に答える 2

2

依存性注入の鍵は、すべてのハード依存性をコンストラクターで指定する必要があることです。

したがって、コンストラクターは次のようになります。

- (id)initWithModel:(Model *)model locationManager:(LocationManager *)locationManager objectStore:(ObjectStore *)objectStore;

オプションではなく、クラス自体が構築を担当してはならない機能を提供するものはすべて、コンストラクターで指定する必要があります。これは、オブジェクト自体に、ネットワークソケット、ストレージバックエンドなどを分離するための依存関係のモックバージョンを提供できるため、テストにも非常に役立ちます。

これが面倒で、コンストラクターが大きすぎるか醜いように見える場合、これはプロパティまたは暗黙の依存関係である必要があるためではありません。これはおそらく、クラスがやりすぎを試みているか、その依存関係を複合オブジェクトに織り込んでいる可能性があることを意味します。

私は非常に大規模な(100,000ライン)本番iPhoneアプリケーションに取り組んでおり、テスト駆動開発に非常に固執しています。私が依存性注入を実現する手段は、実際にはプロトコルを介したものです。あなたは、インターネット上で見られるサンプルコードがこのように書かれていることはほとんどないというあなたの観察に完全に正しいですが、それは100%正確で適切です。

私がいつも使用している本当に便利なパターンは、2つのコンストラクターを使用することです。1つはすべての依存関係を公開し、もう1つはコンストラクターパラメーターを少なくし、暗黙のデフォルト(本番環境で使用)を使用します。

例えば:

// Fully exposed constructor, for easy unit testing.
- (id)initWithHost:(NSString *)host storageProvider:(id<StorageProvider>)storageProvider socketFactory:(id<SocketFactory>)socketFactory;

// Constructor that calls the former, with fully-functional defaults passed into former constructor implicitly.
- (id)initWithHost;

このパターンを優れたモックフレームワーク(OCMockitoまたはOCMockはどちらも優れています)と組み合わせると、クリーンで正直でテスト可能なコードを設計するのに非常に役立ちます。:)

于 2012-08-02T04:46:05.857 に答える
1

異議はあなたが探している答えかもしれません。これは、手動の依存性注入に代わるものを提供する依存性注入フレームワークです。

例えば、

@implementation MyViewController
objection_register(MyViewController)
objection_initializer(initWithNibName:bundle:, @"MyNibName")
objection_requires(@"model", @"locationManager", @"objectStore", @"objectFactory")

@synthesize model;
@synthesize locationManager;
@synthesize objectStore;
@synthesize objectFactory;
@end

インジェクターを使用して初期化する場合(詳細については、ガイドを参照してください):

MyViewController *controller = [self.objectFactory getObject:[MyViewController class]];
[self.navigationController pushViewController:controller animated:YES];

反対意見は、「コンストラクター」インジェクションよりもプロパティインジェクションを使用することを支持します。主な理由は、Objective-Cには言語の一部としてコンストラクターがないためです(alloc init慣例として使用されます)。Objective-Cランタイムは、メッセージのパラメーターに関するタイプ情報をほとんど提供しません。ただし、ランタイムはプロパティに関する豊富な情報を提供し、Key-ValueコーディングAPIは強力であり、NSInvocationよりも一般的に安全です。

于 2012-08-03T01:24:37.550 に答える