Open Closed Principle or the cure to the framework dependency disease

Hi there bugs.

Today's topic is about a nice architectural pattern.
Imagine you have to provide users to log into your app(software) with Facebook and Instagram

How do you organise your code in classes in a extensible manner so that later you can later add Twitter to the login options?

If you have not written code before, it must seem fairly simple to add the ability to login with twitter once you have instagram and facebook. Ideally you will do the same thing you did for those two, and just copy paste it for twitter. How hard can this be?

In fact it's very hard if you don't separate code properly. Let's look how things can go wrong.

Suppose you have an app, with one screen which provides login with Instagram. Basic stuff:

#define APP_ID @"fd725621c5e44198a5b8ad3f7a0ffa09"
@implementation IGViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    IGAppDelegate* appDelegate = (IGAppDelegate*)[UIApplication sharedApplication].delegate;
    appDelegate.instagram = [[Instagram alloc] initWithClientId:APP_ID
                                                       delegate:nil];
    appDelegate.instagram.sessionDelegate = self;
}
- (IBAction)login:(id)sender {
    IGAppDelegate* appDelegate = (IGAppDelegate*)[UIApplication sharedApplication].delegate;
    [appDelegate.instagram authorize:[NSArray arrayWithObjects:@"comments", @"likes", nil]];
}
- (void)igDidLogin {
    IGListViewController* viewController = [[IGListViewController alloc] init];
    [self.navigationController pushViewController:viewController animated:YES];
}
@end

First we do some setup for instagram in viewDidLoad. Then when the login button(login:) is pressed we go to login with instagram. If successful we execute the igDidLogin method and show the user a new screen.

We knew from the beginning we were going to have facebook login as well. So let's try and squeze that into this screen and see what happens.

#import "IGViewController.h"
#import "IGAppDelegate.h"
#import "IGListViewController.h"
#import <FBSDKCoreKit/FBSDKCoreKit.h>
#import <FBSDKLoginKit/FBSDKLoginKit.h>

#define APP_ID @"fd725621c5e44198a5b8ad3f7a0ffa09"
@implementation IGViewController
- (void)viewDidLoad {
    [super viewDidLoad];   
    IGAppDelegate* appDelegate = (IGAppDelegate*)[UIApplication sharedApplication].delegate;
    appDelegate.instagram = [[Instagram alloc] initWithClientId:APP_ID
                                                       delegate:nil];
    appDelegate.instagram.sessionDelegate = self;
}
- (IBAction)login:(id)sender {
    IGAppDelegate* appDelegate = (IGAppDelegate*)[UIApplication sharedApplication].delegate;
    [appDelegate.instagram authorize:[NSArray arrayWithObjects:@"comments", @"likes", nil]];
}
- (IBAction)loginWithFacebook:(id)sender {
    FBSDKLoginManager *login = [[FBSDKLoginManager alloc] init];
    [login logInWithReadPermissions: @[@"public_profile"]
                 fromViewController:self
                            handler:^(FBSDKLoginManagerLoginResult *result, NSError *error) {
                                if (error) {
                                    NSLog(@"Process error");
                                } else if (result.isCancelled) {
                                    NSLog(@"Cancelled");
                                } else {
                                    NSLog(@"Logged in");
                                }
                            }];
}
#pragma - IGSessionDelegate
-(void)igDidLogin {
    IGListViewController* viewController = [[IGListViewController alloc] init];
    [self.navigationController pushViewController:viewController animated:YES];
}
@end

Phew, squeezed that in

Now it should start bothering you. Imagine you had to add twitter login, how many lines of code and complexity would that add?
A lot. It would look something like this:

- (void)succesLogin {
    NSString *loggedInUserName;
    NSString *apiToken;
    if ([FBSDKLoginManager isLoggedIn]) {
        loggedInUserName = [FBSDKLoginManager loggedInUser];
        apiToken = [FBSDKLoginManager loggedInUser];
    } else if ([Instagram isUserLoggedIn]) {
        loggedInUserName = [Instagram sharedUser];
        apiToken = nil;
    }
}

Okay. So having everything in a ViewController class is not a good strategy. Speaking of strategies:

Title

This is the strategy pattern. It was originally introduced by the Gang of four. It looks like it could save us. What if we had an InstagramAuthenticator, and a FacebookAuthenticator, and just call them using an interface.

The strategy pattern speaks to the larger idea of the Open Closed principle which says that a system should be open for extension but closed for modification.

Translation: You should be able to add new ways to login into your app without changing the source code. Sounds unbelievable.

I also didn't believe this so here is a Demo

Okay, so here's how the view controller has been transformed so that you can change it's behaviour without changing it's source code:

#import "IGViewController.h"
#import "IGListViewController.h"

@interface IGViewController() <AuthenticationDelegate>
@property (strong, nonatomic) id<Authenticator> authenticator;
@end

@implementation IGViewController

- (IBAction)login:(id)sender {
    [self performLogin];
}
- (IBAction)loginWithFacebook:(id)sender {
    [self performLogin];
}
- (void)performLogin {
    [self.authenticator setDelegate:self];
    [self.authenticator login];
}
#pragma - AuthenticationDelegate
- (void)authenticationDidSucceedWithUserName:(NSString *)username {
    IGListViewController* viewController = [[IGListViewController alloc] init];
    [self.navigationController pushViewController:viewController animated:YES];
}
- (void)authenticationDidFail {
    NSLog(@"Error");
}
@end

The View Controller now knows about an Interface. The Authenticator interface. (Quickly check the strategy pattern UML diagram).

Here is the interface:

@protocol Authenticator <NSObject>
- (void)login;
- (void)setDelegate:(id<AuthenticationDelegate>)delegate;
@end

And you can implement this interface and pass that object to the view controller. That's how you extend it's behaviour without changing the view controller source code.

@interface RandomAuthenticator()
@property (weak, nonatomic) id<AuthenticationDelegate> delegate;
@end

@implementation RandomAuthenticator

- (void)login {
    if ([self randomValue] > 1000) {
        [self.delegate authenticationDidSucceedWithUserName:@"Dan"];
    } else {
        [self.delegate authenticationDidFail];
    }
}

- (void)setDelegate:(id<AuthenticationDelegate>)delegate {
    _delegate = delegate;
}

- (int)randomValue {
    int value = rand();
    return value;
}
@end

But also, don't forget to use this random authenticator

    IGViewController *topVC = [self topViewController];
    topVC.authenticator = [[RandomAuthenticator alloc] init];

You see how we changed the view controller's behaviour without it even knowing? Open Closed principle at it's best.

How does this tie into framework dependency disease? Well, whenever you add a new external library you should use the same pattern, create an interface, abstract yourself from that specific framework. Whenever a new, better framework comes around, you can ditch the old one and use the new one instantly.

The moral of the story is that loose coupling is nice

Advantages of the Open Closed principle:
1. You can split work between the UI, and logging mechanism.
2. The login mechanism is completely independent of the UI, so you can always reuse it in another part of the or in totally different app.
3. Team members can work on different flavours of login at the same time.
4. You can estimate how much time it takes to implement instagram login and facebook login. Then decide based on your needs and resources.
5. You can always add new logging mechanism by changing nothing else.
6. Your ViewController is much smaller and nicer.
7. Classes have a single responsibility.
8. You can work with a Mocked authenticator until a real one is developed. So you can develop the UI First and not be blocked.
9. You can Unit test, by mocking the authenticator.
10. Do i need to continue?

The Code Bug

A passionate iOS developer. Looking to radically improve the way we all develop software.

Amsterdam