2

我看到了在视图控制器(注入)之间传递依赖关系而不是使用全局状态的好处。然而,我很好奇人们如何在实践中实现这一点。

在最简单的情况下,很容易在两个视图控制器之间传递一个模型:

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

但是,我发现扩展这种方法存在两个问题:

1)您可能有一些所有视图控制器都需要的服务(位置管理器、对象存储等)。因此,您最终会得到一个必须记住为每个视图控制器设置的依赖项列表:

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

2)第二个问题与第一个问题有关:您实际上并没有强制在推送视图控制器之前设置这些依赖项。我想您可以编写一个需要所有依赖项的 init 方法,但您仍然无法强制执行它。它也将是冗长的,如果您想稍后添加依赖项,那将是一个巨大的痛苦。

处理这些问题的方法是什么?似乎没有多少 Obj-C 人使用依赖注入框架。我能想到的一种方法是创建一个包含所有共享依赖项的 AppContext 类,然后将其传递给所有视图控制器。

此外,通常您使用接口而不是实现来声明依赖项(至少在 Java 中),因此您可以模拟它们以进行单元测试。我没有看到很多 Obj-C 人以这种方式使用协议。然后你如何模拟单元测试的依赖关系?

4

2 回答 2

2

依赖注入的关键是所有硬依赖都应该在构造函数中指定!

因此,您的构造函数应如下所示:

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

任何不是可选的并提供类本身不应该负责构造的设施的东西都应该在构造函数中指定。这对测试也有很大帮助,因为可以为对象本身提供其依赖项的模拟版本,以隔离网络套接字、存储后端等……

如果这看起来很麻烦并且你觉得构造函数看起来太大或太丑,这不是因为这些应该是属性或隐式依赖!这可能意味着该类试图做太多事情,或者它的依赖关系可以被分解为复合对象。

我在一个非常大的(100,000 行)生产 iPhone 应用程序上工作,并且非常坚持测试驱动开发。实际上,我实现依赖注入的方法是通过协议。您的观察完全正确,您在互联网上看到的示例代码很少是这样编写的,但它是 100% 正确和适当的。

我一直使用的一个非常有用的模式是有两个构造函数,一个暴露所有依赖项,一个具有更少的构造函数参数和隐式默认值(用于生产)。

例如:

// 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 Coding API比 NSInvocation 更强大且通常更安全。

于 2012-08-03T01:24:37.550 回答