Skip to content

Instantly share code, notes, and snippets.

@AliSoftware
Last active December 28, 2015 14:09
Show Gist options
  • Save AliSoftware/7513003 to your computer and use it in GitHub Desktop.
Save AliSoftware/7513003 to your computer and use it in GitHub Desktop.
Dependency injection concepts
// No dependency injection at all.
// Instead of having independant modules, we have a strong dependency between the Controller, the ContactService and the View
@interface Contact : NSObject
@property NSString* name;
// …
@end
@interface ContactService : NSObject
- (void)fetchAllContactsWithCompletion:(void(^)(NSArray* contacts))completion;
+ (instancetype)sharedInstance;
// …
@end
@interface ControllerWithoutDI : NSObject
@property NSArray* contacts; // Array of Contact objects
@property UITableView* tableView;
@end
//-----------------------------------------------------------------------------
@implementation ControllerWithoutDI
- (id)init
{
if ((self = [super init]) != nil)
{
[[ContactService sharedInstance] fetchAllContactsWithCompletion:^(NSArray* contacts) {
self.contacts = contacts;
[self.tableView reloadData];
}];
}
return self;
}
// …
@end
//-----------------------------------------------------------------------------
/* Drawbacks:
- we have a dependancy with ContactService,
- we need to make ContactService a singleton,
- we lack flexibility in case we want to change the ContactService used
- we lack flexibility if we want to change the vue and use something else than a UITableView
*/
// Dependency injection on the ContactService, allowing us to choose which one we want to use
// Allows more flexibility and a weak dependency between module instead of a strong one
@protocol ContactService <NSObject>
- (void)fetchAllContactsWithCompletion:(void(^)(NSArray* contacts))completion;
@end
@interface ControllerWithMinimumDI : NSObject
@property id<ContactService> contactService;
@property UITableView* tableView;
@end
//-----------------------------------------------------------------------------
@implementation ControllerWithMinimumDI
- (id)initWithContactService:(id<ContactService>)contactService
{
if ((self = [super init]) != nil)
{
self.contactService = contactService;
[contactService fetchAllContactsWithCompletion:^(NSArray *contacts) {
[self.tableView reloadData];
}];
}
return self;
}
// …
@end
//-----------------------------------------------------------------------------
// We can then imagine multiple different ContactServices, like those (which are NOT singletons, contrary to NoDependencyInjection.m example):
@interface GMailContactService : NSObject <ContactService>
- (instancetype)initWithLogin:(NSString*)login password:(NSString*)password apiKey:(NSString*)apiKey;
@end
@interface YahooContactService : NSObject <ContactService>
- (instancetype)initWithLogin:(NSString*)login password:(NSString*)password;
@end
// and so on…
// We can even imagine a ContactServiceAggregator, which itself conforms to theContactService protocol
// and ask for its underlying ContactServices (GMail, Yahoo, …) for all the contacts to then aggregate them and return them like one
@interface ContactServiceAggregator : NSObject <ContactService>
- (instancetype)initWithContactServices:(NSArray*)contactServices; // array of id<ContactService> instances
@end
// Extreme Dependency Injection concept pushed further (maybe a bit too much further? You need to find a compromise)
// This allows a total flexibility, including on the display to use to show the contacts
// - We use a protocol for ContactService to allow different instance conforming to this protocol to be used
// - We use a protocol for ContactView to allow different kind of displays to be used, including Interactive vs. Non-Interactive ones
// - We use a protocol for the Contact model object to allow different models to be used
// So this is highly generic
@protocol Contact <NSObject>
@property NSString* name;
@end
@protocol ContactService <NSObject>
- (void)fetchAllContactsWithCompletion:(void(^)(NSArray* contacts))completion; // array of id<Contact>
@end
@protocol ContactView <NSObject>
- (void)addContact:(id<Contact>)contact;
- (void)performBatchChanges:(dispatch_block_t)changesBlock;
@end
@protocol InteractiveContactView <ContactView>
- (void)addContact:(id<Contact>)contact action:(dispatch_block_t)action;
@end
@interface ControllerWithFullDI : NSObject
@property id<ContactService> contactService;
@property id<ContactView> contactView;
@end
//-----------------------------------------------------------------------------
@implementation ControllerWithFullDI
- (id)initWithContactService:(id<ContactService>)contactService contactView:(id<ContactView>)contactView
{
if ((self = [super init]) != nil)
{
self.contactService = contactService;
self.contactView = contactView;
[contactService fetchAllContactsWithCompletion:^(NSArray *contacts) {
[contactView performBatchChanges:^{
for(Contact* contact in contacts)
{
if ([contactView conformsToProtocol:@protocol(InteractiveContactView)])
{
[(id<InteractiveContactView>)contactView addContact:contact action:^{
NSLog(@"Contact %@ touched!", contact);
}];
}
else
{
[contactView addContact:contact];
}
}
}];
}];
}
return self;
}
// …
@end
//-----------------------------------------------------------------------------
// Then we can imagine having multiple different "views" to display a list of contacts
// (1) An UIView for iPhone : it uses a UITableView, and `performBatchChanges` empty the NSArray, execute the `changesBlock`, then calls `reloadData` at the end
@interface iPhoneScreenContactView : UIView <InteractiveContactView> @end
// (2) An UIView for iPad : the same but with an UICollectionView
@interface iPadScreenContactView : UIView <InteractiveContactView> @end
// (3) Console output : simply NSLogs the contacts. `performBatchChanges` does a clearScreen before executing the `changesBlock`. Isn't interactive.
@interface ConsoleContactView : NSObject <ContactView> @end
// (4) Printer output : `performBatchChanges` initializes the printer, executes the `changesBlock` to fill the page layout, then prints the result at the end
@interface PrinterContactView : NSObject <ContactView> @end
// and so on…
/* Advantages:
- Flexible all the way.
- We can use it with various ContactServices, even a ContactServiceAggregator (or not)
- We can use it with various Output objects, including various UIViews, console, printer, …
- We can use it even if the internals of the model objects changes (for example if GMailContact and YahooContact classes have some different implementations)
Drawbacks:
- Needs more intermediate @protocols, so is a bit more verbose
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment