Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Factory provider #101

Merged
merged 3 commits into from
Nov 12, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions Source/Component/FactoryProvider/TyphoonAssistedFactoryBase.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
////////////////////////////////////////////////////////////////////////////////
//
// TYPHOON FRAMEWORK
// Copyright 2013, Jasper Blues & Contributors
// All Rights Reserved.
//
// NOTICE: The authors permit you to use, modify, and distribute this file
// in accordance with the terms of the license agreement accompanying it.
//
////////////////////////////////////////////////////////////////////////////////

#import <Foundation/Foundation.h>

/**
* Internal base class for all Typhoon assisted factories. Users should not use
* this class directly.
*/
@interface TyphoonAssistedFactoryBase : NSObject

/** Used internally by the setters of the properties in the subclasses */
- (void)setInjectionValue:(id)value forProperty:(NSString *)property;

/** Used internally by the getters of the properties in the subclasses */
- (id)injectionValueForProperty:(NSString *)property;

/** Used to get the type encoding during the construction of subclasses */
- (id)_dummyGetter;

/** Used to get the type encoding during the construction of subclasses */
- (void)_setDummySetter:(id)value;

@end
43 changes: 43 additions & 0 deletions Source/Component/FactoryProvider/TyphoonAssistedFactoryBase.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
////////////////////////////////////////////////////////////////////////////////
//
// TYPHOON FRAMEWORK
// Copyright 2013, Jasper Blues & Contributors
// All Rights Reserved.
//
// NOTICE: The authors permit you to use, modify, and distribute this file
// in accordance with the terms of the license agreement accompanying it.
//
////////////////////////////////////////////////////////////////////////////////

#import "TyphoonAssistedFactoryBase.h"

@implementation TyphoonAssistedFactoryBase
{
NSMutableDictionary *_injections;
}

- (instancetype)init
{
self = [super init];
if (self)
{
_injections = [[NSMutableDictionary alloc] init];
}

return self;
}

- (void)setInjectionValue:(id)value forProperty:(NSString *)property
{
[_injections setObject:value forKey:property];
}

- (id)injectionValueForProperty:(NSString *)property
{
return [_injections objectForKey:property];
}

- (id)_dummyGetter { return nil; }
- (void)_setDummySetter:(id)value {}

@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
////////////////////////////////////////////////////////////////////////////////
//
// TYPHOON FRAMEWORK
// Copyright 2013, Jasper Blues & Contributors
// All Rights Reserved.
//
// NOTICE: The authors permit you to use, modify, and distribute this file
// in accordance with the terms of the license agreement accompanying it.
//
////////////////////////////////////////////////////////////////////////////////

#import <Foundation/Foundation.h>

@class TyphoonAssistedFactoryDefinition;

/** Used to configure the TyphoonAssistedFactoryDefinition passed as argument */
typedef void(^TyphoonAssistedFactoryDefinitionBlock)(TyphoonAssistedFactoryDefinition *definition);

/** Used to enumerate over factory method selectors and their associated body blocks */
typedef void(^TyphoonAssistedFactoryMethodsEnumerationBlock)(SEL name, id body);

@interface TyphoonAssistedFactoryDefinition : NSObject

/**
* Define a new factory method with the given selector and associating the given
* block. The block should return a value. The arguments of the block are the
* arguments of the factory method in the same order, but the factory itself is
* prefixed as first argument, so the factory method arguments are the second
* and following arguments.
*/
- (void)factoryMethod:(SEL)name body:(id)bodyBlock;

#pragma mark - Internal methods

/**
* The number of factory methods defined for this assisted factory. Users should
* not invoke this method directly.
*/
@property (nonatomic, assign, readonly) NSUInteger countOfFactoryMethods;

/**
* Configure this assisted factory definition inside the block provider as
* argument. Users should not invoke this method directly.
*/
- (void)configure:(TyphoonAssistedFactoryDefinitionBlock)configurationBlock;

/**
* Enumerate over all the defined factory method. The block will be invoked once
* per factory method, receiving the selector and the body block associated for
* each factory method. Users should not invoke this method directly.
*/
- (void)enumerateFactoryMethods:(TyphoonAssistedFactoryMethodsEnumerationBlock)enumerationBlock;

@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
////////////////////////////////////////////////////////////////////////////////
//
// TYPHOON FRAMEWORK
// Copyright 2013, Jasper Blues & Contributors
// All Rights Reserved.
//
// NOTICE: The authors permit you to use, modify, and distribute this file
// in accordance with the terms of the license agreement accompanying it.
//
////////////////////////////////////////////////////////////////////////////////

#import "TyphoonAssistedFactoryDefinition.h"

@implementation TyphoonAssistedFactoryDefinition

{
NSMutableArray *_factoryMethods;
}

- (instancetype)init
{
self = [super init];
if (self)
{
_factoryMethods = [[NSMutableArray alloc] init];
}

return self;
}

- (NSUInteger)countOfFactoryMethods
{
return [_factoryMethods count];
}

- (void)configure:(TyphoonAssistedFactoryDefinitionBlock)configurationBlock
{
configurationBlock(self);
}

- (void)factoryMethod:(SEL)name body:(id)bodyBlock
{
[_factoryMethods addObject:@[NSStringFromSelector(name), bodyBlock]];
}

- (void)enumerateFactoryMethods:(TyphoonAssistedFactoryMethodsEnumerationBlock)enumerationBlock
{
for (NSArray *factoryMethodPair in _factoryMethods)
{
enumerationBlock(NSSelectorFromString(factoryMethodPair[0]), factoryMethodPair[1]);
}
}

@end
113 changes: 113 additions & 0 deletions Source/Component/FactoryProvider/TyphoonFactoryProvider.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
////////////////////////////////////////////////////////////////////////////////
//
// TYPHOON FRAMEWORK
// Copyright 2013, Jasper Blues & Contributors
// All Rights Reserved.
//
// NOTICE: The authors permit you to use, modify, and distribute this file
// in accordance with the terms of the license agreement accompanying it.
//
////////////////////////////////////////////////////////////////////////////////

#import <Foundation/Foundation.h>

#import "TyphoonDefinition.h"
#import "TyphoonAssistedFactoryDefinition.h"

/**
* Provides a factory that combines the convenience method arguments with the
* assembly-supplied dependencies to construct objects.
*
* To create a factory you must define a protocol for the factory, wire its
* dependencies (defined as readonly properties), and provide implementation
* blocks for the body of the class methods. It is not as automatic as we will
* like, but at least it avoids a lot of tedius and repetitive boilerplate.
*
* # Example
*
* Imagine you have defined this Payment class with two dependencies that should
* be injected, and two parameters that must be provided at runtime.
*
* @interface Payment : NSObject
*
* - (instancetype)initWithCreditService:(id<CreditService>)creditService
* authService:(id<AuthService>)authService
* startDate:(NSDate *)date
* amout:(NSUInteger)amount;
*
* @end
*
* You also define a protocol for the factory of the Payment objects. The factory
* must declare the dependencies as readonly properties, and the factory methods
* should be the only ones existing.
*
* @protocol PaymentFactory <NSObject>
*
* @property (nonatomic, strong, readonly) id<CreditService> creditService;
* @property (nonatomic, strong, readonly) id<AuthService> authService;
*
* + (Payment *)paymentWithStartDate:(NSDate *)startDate
* amount:(NSUInteger)amount;
*
* @end
*
* Then, in your assembly file, you define the PaymentFactory component using the
* TyphoonFactoryProvider as the following code:
*
* - (id)paymentFactory {
* return [TyphoonFactoryProvider withProtocol:@protocol(PaymentFactory) dependencies:^(TyphoonDefinition *definition) {
* [definition injectProperty:@selector(creditService)];
* [definition injectProperty:@selector(authService)];
* } factories:^(TyphoonAssistedFactoryDefinition *definition) {
* [definition factoryMethod:@selector(paymentWithStartDate:amount:) body:^id (id<PaymentFactory> factory, NSDate *startDate, NSUInteger amount) {
* return [[Payment alloc] initWithCreditService:factory.creditService authService:factory.authService startDate:startDate amount:amount];
* }];
* }];
* }
*
* In the dependencies block you can use any `injectProperty:` method that you
* will use for your normal definitions. It is a standard TyphoonDefinitionBlock
* that get passed directly to the TyphoonDefinition constructor used internally.
*
* For the factories block you must provide one body block for each class method
* of your factory protocol. The block used as the body receives the factory
* itself as the first argument, so you can use the factory properties, and then
* the rest of the class method arguments in the second and following positions.
*
* In the case of the protocol having only one factory method you can use a
* shorter version of the method. For example the equivalent shorter version for
* the above definition will be the following one:
*
* - (id)paymentFactory {
* return [TyphoonFactoryProvider withProtocol:@protocol(PaymentFactory) dependencies:^(TyphoonDefinition *definition) {
* [definition injectProperty:@selector(creditService)];
* [definition injectProperty:@selector(authService)];
* } factory^id (id<PaymentFactory> factory, NSDate *startDate, NSUInteger amount) {
* return [[Payment alloc] initWithCreditService:factory.creditService authService:factory.authService startDate:startDate amount:amount];
* }];
* }
*
* Know limitation: You can only create one factory for a given protocol.
*/
@interface TyphoonFactoryProvider : NSObject

/**
* Creates a factory definition for a given protocol, dependencies and factory
* block. The protocol is supposed to only have one class method, otherwise this
* method will fail during runtime.
*/
+ (TyphoonDefinition *)withProtocol:(Protocol *)protocol
dependencies:(TyphoonDefinitionBlock)dependenciesBlock
factory:(id)factoryBlock;

/**
* Creates a factor definition for a given protocol, dependencies and a list of
* factory methods. The protocol is supposed to have the same number of class
* methods, and with the same selectors as defined in the factories block,
* otherwise this method will fail during runtime.
*/
+ (TyphoonDefinition *)withProtocol:(Protocol *)protocol
dependencies:(TyphoonDefinitionBlock)dependenciesBlock
factories:(TyphoonAssistedFactoryDefinitionBlock)definitionBlock;

@end
Loading