Yalantis: iOS, Android And Web App Development Company

How to Achieve Loose Coupling?

All developers know that tightly coupled code elements represent a heavyweight complexity. The code written this way is hard to support, to test and to reuse. If you happen to be new to the project, there is a little to no hope you will find a way out. Every alteration introduced during development may bring about new implicit changes without any visible roots you can trace.

Most developers prefer to remain aloof from the possible issues in the code as long as everything seems to be working just fine at the given moment. However, tech geeks do not look for easy ways. Since we are constantly improving the quality of our code, here you go with an article that will light the way to reducing dependencies.

Inversion of control

In order to get rid of the coupled code troubles we would need to apply the principles of inversion of control.

Inversion is some kind of an abstract principle or a set of recommendations for writing loosely coupled code. The main idea is to make every component of the system as much isolated from others as it is possible. To make it happen each such component should not depend on the details of the exact implementation of other components.

Let’s take a look at a simple example:

@implementation MovieLister

- (NSArray *)moviesDirectedByDirector:(NSString *)director {
    NSArray *allMovies = [finder findAllMovies];

    NSMutableArray *result = [NSMutableArray array];
    for (Movie *movie in allMovies) {
        if ([movie.director isEqualToString:director]) {
            [result addObject:movie];
        }
    }
    return result;
}

We have a MovieLister class that has a method that gets a list of movies from a certain director. So we get a list of all the movies inside this method and then filter them according to the directors.

What is Finder and where it came from?

NSArray *allMovies = [finder findAllMovies];

Finder is an object that can return the list of all the available movies. Let’s add Finder implementation that returns the list of movies from the text file:

@interface TextFileMovieFinder : NSObject

- (id)initWithTextFileName:(NSString *)textFileName;
- (NSArray *)findAllMovies;

@end

We need to have Finder inside the method to get the list of the movies. How can we get the Finder inside the method?

- (NSArray *)moviesDirectedByDirector:(NSString *)director {
    TextFileMovieFinder *finder = [[TextFileMovieFinder alloc] initWithTextFileName:@"movies.txt"];
    NSArray *allMovies = [finder findAllMovies];

    NSMutableArray *result = [NSMutableArray array];
    for (Movie *movie in allMovies) {
        if ([movie.director isEqualToString:director]) {
            [result addObject:movie];
        }
    }
    return result;
}

The simplest and wrong solution is to create the required object inside the method.

In this case the method fully depends on the exact implementation of the exact class TestFileMovieFinder.

TextFileMovieFinder *finder = [[TextFileMovieFinder alloc] initWithTextFileName:@"movies.txt"];
NSArray *allMovies = [finder findAllMovies];

Speaking about the method, it’s enough for us to know that a certain Finder implements a method findAllMovies. In order to do it, we can declare a corresponding protocol and create Finder beyond this method.

@protocol MovieFinder <NSObject>

- (NSArray *)findAllMovies;

@end

@interface TextFileMovieFinder : NSObject <MovieFinder>

- (id)initWithTextFileName:(NSString *)textFileName;

@end
@interface MovieLister : NSObject

@property (nonatomic, strong) id<MovieFinder> finder;

@end

@implementation MovieLister

- (id)init {
    self = [super init];
    if (self) {
        _finder = [[TextFileMovieFinder alloc] initWithTextFileName:@"movies.txt"];
    }
    return self;
}

- (NSArray *)moviesDirectedByDirector:(NSString *)director {
    NSArray *allMovies = [self.finder findAllMovies];
NSMutableArray *result = [NSMutableArray array];
    for (Movie *movie in allMovies) {
        if ([movie.director isEqualToString:director]) {
            [result addObject:movie];
        }
    }
    return result;
}

@end

Disadvantages of this implementation:

  • even if the method doesn’t know about a real Finder class, the class does.
  • there is no possibility to use another Finder, such as the one that works with SQL database or XML when you reuse the given class.

The scheme of dependencies in such implementation.

Dependency injection

As you can see in the scheme, MovieLister works with exact implementation of MovieFinder though the protocol. Besides, it helps to create this exact implementation and obviously, knows the exact class of MovieFinder, which is not what we are trying to do here.

So how to get rid of this dependency? We can take a look at the following popular patterns that implement inversion of control:

1. Dependency Injection 2. Service Locator

Dependency injection

The main idea is to have a separate object (Assembler) that places the required implementation of MovieFinder into the MovieLister class.

The scheme of dependencies if you use DI.

Screenshot 2014-12-19 11.53.53

Again, MovieLister works with the exact implementation of MovieFinder though the protocol. But now there is an assembler that takes care of the creation of the exact MovieFinder. Thus, MovieLister gets the already created object from the outside.

Now let’s take a look at the types of DI to get a better understanding of how to use it.

Constructor injection

- (id)initWithMovieFinder:(id<MovieFinder>)finder {
    self = [super init];
    if (self) {
        _finder = finder;
    }
    return self;
}

In this case, the exact implementation of MovieFinder is passed to the constructor as a parameter.

Setter injection

@interface MovieLister : NSObject
- (void)setFinder:(id<MovieFinder>)finder;
@end

@interface MovieLister ()
@property (nonatomic, strong) id<MovieFinder> finder;
@end

Exact implementation of MovieFinder is passed through the setter method.

Interface injection

@protocol Injectable <NSObject>
- (NSArray *)findAllMovies;
@end

@interface TextFileMovieFinder : NSObject <Injectable>
- (id)initWithTextFileName:(NSString *)textFileName;
@end

@protocol InjectDependent <NSObject>
- (void)injectDependency:(id<Injectable>)dependency;
@end

@interface MovieLister : NSObject <InjectDependent>
@property (nonatomic, strong) id<Injectable> finder;
@end

@implementation MovieLister
- (void)injectDependency:(id<Injectable>)dependency {
    self.finder = dependency;
}

All the objects that need to be “injected” have to implement the protocol. Let it be Injectable protocol, for example. The objects you need to do the injection in have to implement the protocol InjectDependent. The injection itself is performed by using the method from this protocol.

Now let’s see what Service Locator is.

Service Locator

The main idea is to have an object that knows and contains all the services the app needs. In our case Service Locator should have a method that will return MovieFinder into MovieLister.

The scheme of dependencies if you use Service Locator:

Dependency injection, service locator

As in the scheme with DI, assembler takes care of the creation of the exact MovieFinder. Then the assembler passes the created implementation to the Service Locator which then ends up in the MovieLister.

@interface ServiceLocator : NSObject

@property (nonatomic, strong, readonly) id<MovieFinder> movieFinder;

@end

@interface MovieLister : NSObject

@property (nonatomic, weak) ServiceLocator *serviceLocator;

@end

@implementation MovieLister

- (NSArray *)moviesDirectedByDirector:(NSString *)director {
    id<MovieFinder> finder = [self.serviceLocator movieFinder];
    NSArray *allMovies = [finder findAllMovies];

    NSMutableArray *result = [NSMutableArray array];
    for (Movie *movie in allMovies) {
        if ([movie.director isEqualToString:director]) {
            [result addObject:movie];
        }
    }
    return result;
}

@end

As seen here, MovieLister has access to Service Locator where it gets MovieFinder. It is worth noting that this is an example of static Service Locator implementation, the one that has a getter for each service. Otherwise, you can use a dynamic Service Locator.

@interface ServiceLocator : NSObject

- (void)setService:(id)service forKey:(NSString *)key;
- (id)serviceForKey:(NSString *)key;

@end

Access to all the services in the dynamic Service Locator comes through a unique for every service key. However, the objects which want to get the service from Service Locator need to know this key, which is a disadvantage of this implementation.

The options of how Service Locator can get into classes:

  • Service Locator is declared as singletone (Global Locator). It can be accessible from any place in the code.
  • Service Locator is thrown into classes while using Dependency Injection

Service Locator or DI?

Hey! Service Locator happens to be a pattern and an antipattern at the same time. How did that happen? We had to face a number of problems, which made us think that there is something wrong with Service Locator. Here are those issues:

  • Explicit dependency of classes from Service Locator. Even though we don’t know the exact implementation of services (as we work with services though protocols), there is a dependency from the exact implementation of the Service Locator.
  • Dependencies are hidden inside the implementation of the class. When using Service Locator, the interface of the class doesn’t have all the dependencies visible. You can only see the dependency from Service Locator itself. By simply looking at the interface you don’t know what is needed from this Locator to make the class do its job. To understand the dependencies you would need to dig into the implementation. This disadvantage worsens reusability of such classes.
  • When you reuse the classes dependent upon Service Locator in other projects where Service Locator is already being used, you need to bind their interfaces. This is usually done through Adapter pattern, but it needs extra time and efforts.
  • Since the class gets all the Service Locator at its disposal, it has access to all the services available. In this case, it’s hard to control the exact number of services using this class. This is because the use of these services is spread all over the implementation. When we expand the functionality of our class, we start using other services without noticing that the number of dependencies increases.

The conclusion – DI is your bro!

What we’ve learned

  • Constructor injection is perfect for “obligatory” dependencies, without which the class can’t implement its task. This way we locate all the dependencies in one place - in the constructor. When the class expands and starts using additional services, they will appear in the constructor as parameters which cannot be left unnoticed. So when another parameter is added to the constructor and the number of all the parameters becomes higher than 4, you need to think about the architecture of your class.
  • Setter injection (property injection) fits “optional” dependencies, those which have a reasonable implementation known to the class by default. At the same time there has to be a possibility to change the dependency while the class is working and without any negative consequences.
  • Interface injection is used very rarely because it requires creation of a large number of interfaces. Usually two previous options are sufficient.

If you are already using Service Locator

You can make implicit dependencies more explicit:

@interface MovieLister : NSObject

@property (nonatomic, weak) id<MovieFinder> finder;

@end

@implementation MovieLister

- (id)initWithServiceLocator:(ServiceLocator *)serviceLocator {
    self = [self init];
    if (self) {
        _finder = [serviceLocator movieFinder];
    }
    return self;

In this implementation MovieLister doesn’t have Service Locator. It just uses it to get all the necessary services. Thus, we locate all our dependencies in the constructor. That’s why they are more obvious (not spread all over the implementation). And then we can transfer the services, rather than locator, to the constructor.

@interface MovieLister : NSObject

@property (nonatomic, weak) id<MovieFinder> finder;

@end

@implementation MovieLister

- (id)initWithMovieFinder:(id<MovieFinder>)finder {
    self = [self init];
    if (self) {
        _finder = finder;
    }
    return self;
}

At this stage we only change the declaration of our constructor.

What can you do if you have a big project with tough dependencies or singletones and/or you don’t have a wish to implement DI manually?

DI Frameworks bring magic to DI! Some of the most popular ones include TyphoonAppleGuice and BloodMagic. The idea of frameworks is to avoid implementing injection manually. But in order to do it, you need to configure the framework accordingly.

Using these frameworks simplifies programmer’s life but the process of injection implementation is not always clear. This means you won’t be able to figure it out once you get to work on the already created project. Provided you can do without a framework, try to do stuff manually.

Even though Service Locator turned out to be an antipattern, you’d better use it, than have strong dependencies or singletones. Also the code of the Service Locator is easy to test. But if you can move to Dependency Injection, you should do so!

Read also:

Useful links:

  1. Inversion of Control Containers and the Dependency Injection pattern
  2. DI Паттерны. Service Locator
  3. DI Паттерны. Constructor Injection
  4. DI Паттерны. Property Injection
  5. DI Паттерны. Method Injection

 

 

 

 

Tech

Mastering UIKit Performance

Tech

Declarative Navigation Bar Appearance Configuration

Tech

8 Tips on CoreData Migration

Tech

From JSON to Core Data Fast

Tech

“Tree of Models” as an Alternative App Architecture Model