如何处理应用程序中的共享对象(服务定位器和/或依赖注入)?

时间:2014-11-24 11:41:03

标签: ios objective-c dependency-injection singleton service-locator

如何通过应用程序(服务定位器和/或DI)处理组件共享的accros?

每个应用程序都有我们一次又一次遇到的共同组件。这些可能是DatabaseManager,CacheManager,UserManager,ReportingManager,SettingsManager,BackendAPIManager,CrashManager,....

通常这些组件在我的情况下是类,因为我喜欢将相关的东西封装到组件中,这些组件提供了对代码的其他部分的良好且容易的访问。作为一般的经验法则,我们可以将代码封装到一个类中,只要该类在单个可重复性原则上运行并且它不是太大。

这些类中的一些类似于第三方库的包装 - ReportingManager,CrashManager(Google Analytics,Crashlytics,...),一些封装Web服务调用部分 - BackendAPIManager(用于AFNetworking)。有些也像包装器 - DatabaseManager(核心数据,FMDB),CacheManager(序列化到文件)SettingsManager(NSUserDefault)。

大多数提到的组件/类不存储状态或其他任何东西 - 它们的实现只是封装了行为代码。但有些类会封装行为和状态代码,例如UserManager与BackendAPIManager交互,存储检索到的用户,存储登录状态并具有很好的API方法:login,logout,register,resetPassword,isLoggedIn。

现在,为此创造单身人士的诱惑很大,但我们都知道单身人士对单元测试不利。因此,其中一种可能的方法是使用方法registerObject,getObject创建一种单例注册表(也称为服务定位器设计模式),并在应用程序启动期间创建并注册所有这些对象,然后始终使用via:

[[Registry shared] getObject:@protocol(UserManagerProtocol)];

因此,在测试中,您注销不需要的管理器并通过Registry registerObject注册模拟。服务定位器模式由Martin Fowler描述,并且根据他的说法,它是依赖注入的替代方案。然而,来自.NET世界的另一位杰出作者Mark Seemann声称Service Locator是一个反模式,主要是因为你不必忘记注册新的共享对象,需要知道应用程序是如何工作的,因为Registry隐藏了所有的dependecies并且devs不喜欢重置并注册这些共享对象以进行单元测试。

虽然Service Locator和Dependency注入都有助于将依赖对象与使用代码分开,但依赖注入仍然在各种讨论和论坛中更受欢迎,并且看起来更受欢迎。虽然我以前使用Service Locator作为共享组件,有时候使用DI将非共享对象注入代码,但我对替代共享对象的替代方法感兴趣。一些migh建议并不关心对象的单一性,只是创建新实例并使用依赖注入每次注入,但是复制这些对象的方法很糟糕,如果对象存储状态,有时会导致问题。

我正在使用BaseModel和BaseViewController进行扩展,因此我很容易在这两个类上添加相关的管理器属性以进行依赖注入。我使用了默认值类型DI实现Approach described here

的属性
- (UserManager *)userManager {

  // Lazy instantiation of the default value in case we didn't injected dependency explicitly
  if (!_userManager) {

    _userManager = [[Registry shared] getObject:@protocol(UserManager)];
  }

  return _userManager;
}

这样我就不需要一遍又一遍地将dependecies显式传递给BaseModel和BaseViewController的子类。默认值将从Registry中获取。所有共享对象都将在应用程序启动(ServiceLocator)上注册:

[[Registry shared] register:[UserManager new]];
[[Registry shared] register:[SettingsManager new]];
[[Registry shared] register:[BackendAPIManager new]];
...

然后在其他代码中使用self.userManager,self.settingsManager,self.backendAPIManager来访问它们。

因此,所提出的解决方案使用依赖注入+服务定位器,并且通过我们公开并添加到BaseModel和BaseViewController(@ userManager,@ settingssManager,@ backendAPIManager)的属性,编写单元测试并显式注入和设置依赖非常容易。

您如何看待这种方法?我只是想避免使用DI框架,因为我总是试图保持我的代码干净,易于调试

0 个答案:

没有答案