Last active
December 28, 2015 14:09
-
-
Save AliSoftware/7513003 to your computer and use it in GitHub Desktop.
Dependency injection concepts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 | |
*/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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