How do you deal with shared objects across the app (Service Locator and/or Dependency Injection)?

139 views Asked by At

How do you deal with components shared accros over the app (Service Locator and/or DI)?

Every application has common components that we met over and over again. These might be DatabaseManager, CacheManager, UserManager, ReportingManager, SettingsManager, BackendAPIManager, CrashManager, ....

Typically these components are classes in my case because I like encapsulating related things into components that provide nice and easy acces to other parts of code. As a general rule of thumb, we can encapsulate the code into one class as long as the class operates on single reponsibility principle and it's not too big.

Some of these classes are like wrappers for 3rd party libraries - ReportingManager, CrashManager (Google Analytics, Crashlytics, ...), some encapsulate web services calling part - BackendAPIManager (for AFNetworking). Some also are like wrappers - DatabaseManager (Core data, FMDB), CacheManager (serializing to file) SettingsManager (NSUserDefault).

Most of the mentioned components/classes doesn't store state or anything else - their implementations just encapsulate behavioural code. But sometines, some classes encapsulate both behavioural and state code, for example UserManager interacts with BackendAPIManager, stores retrieved user, stores login state and has nice API methods: login, logout, register, resetPassword, isLoggedIn.

Now, there's a high temptation to create singletons for that, but we all know singletons are bad for unit testing. So, one of the possible ways is to create sort of singleton Registry (also known as Service Locator design pattern) with methods registerObject, getObject and create and register all these objects during app launch and then always use via:

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

Therefore, in your tests you unregister not needed managers and register mocks via Registry registerObject. Service Locator pattern is described by Martin Fowler and according to him it's an alternative to Dependency injection. However, another remarkable author Mark Seemann from .NET world claims Service Locator is an antipattern mostly because you need not forget to register new shared objects, need to know how application works because Registry hides all the dependecies and devs don't like resetting and registering these shared objects for unit tests.

While both Service Locator and Dependency injection helps to separate dependent objects from the usage code, still Dependency Injection is more popular in various discussions and forums and looks like gained more popularity. Though I used Service Locator previously for shared components and sometimes DI for injecting non-shared objects into code, I'm interested for an alternative how I could deal with shared objects. Some migh suggest doesn't care about singleness of the objects, just create new instances and inject everytime using dependency injection, however it's bad approach to duplicate these objects and sometimes will lead to issues if your object stores state.

I'm using and extending from BaseModel, BaseViewController, so it's very easy for me to add related manager properties on those two classes for dependency injection. I'v used property with default value type of DI implementation 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;
}

This way I don't need explicitly pass dependecies to subclasses of BaseModel and BaseViewController over and over again. The default value will be taken from Registry. All the shared objects will be registered on app launch (ServiceLocator):

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

And then use self.userManager, self.settingsManager, self.backendAPIManager inside other code for accesing them.

So the proposed solution uses Dependency Injection + Service Locator, and it's very easy to write unit tests and explicitly inject and set dependecies via properties that we exposed and added to BaseModel and BaseViewController (@ userManager, @ settingssManager, @ backendAPIManager).

What do you think abot this approach? I'm just trying to avoid using DI framework as I always try to keep my code clean and easy to debug

0

There are 0 answers