-
Notifications
You must be signed in to change notification settings - Fork 268
Modularizing Assemblies
Typhoon allows you to group related components together under a TyphoonAssembly sub-class.
- Keep things organized and group related parts of an application assembly.
- Expose an interface containing the public component(s) to be produced. The internal details need not be exposed.
- Allow plugging in different implementations of an assembly when [activating](Activating assemblies).
While grouping components together in logical, modular TyphoonAssembly
units is great, they're not very useful if they are all isolated from each other.
Let's say you have separate TyphoonAssembly
s for each of your application's three main concerns: UI (UIAssembly
), network operations (NetworkComponents
) and persistence (PersistenceComponents
).
In NetworkComponents
you might have a httpClient
that handles RESTful API calls, like so:
@implementation NetworkComponents
- (id<SignUpClient>)httpClient
{
return [TyphoonDefinition withClass:[HttpClient class]
configuration:^(TyphoonDefinition* definition)
{
//etc. . .
}];
}
@end
Now let's suppose we have a signUpViewController
in UIAssembly
that takes a new user's credential information and needs httpClient
to submit the information onto the backend. No problem:
@interface UIAssembly : TyphoonAssembly
// Typhoon will automatically proxy the two collaborating assemblies
// (NetworkComponents and TyphoonAssembly<PersistenceComponents>) here.
@property(nonatomic, strong, readonly) NetworkComponents* networkComponents;
// Collaborating assemblies can be backed by a protocol. We declare
// type using TyphoonAssembly<FactoryProtocol> syntax to tell Typhoon
// that this is a collaborating assembly. In the app's own classes no further
// coupling to Typhoon is necessary, and we may declare a property as type
// id<FactoryProtocol>, avoiding your classes from being aware of Typhoon.
@property(nonatomic, strong, readonly) TyphoonAssembly<PersistenceComponents> persistenceComponents;
// Local components that require components from collaborating assemblies ...
- (RootViewController *)rootViewController;
- (SignUpViewController *)signUpViewController;
- (StoreViewController *)storeViewController;
@end
And now to use a definition from another assembly . . .
```objective-c @implementation UIAssembly
// signUpViewController consumes httpClient component from 'NetworkComponents'
- (SignUpViewController )signUpViewController
{
return [TyphoonDefinition withClass:[SignUpViewController class]
configuration:^(TyphoonInitializer initializer)
{
[definition injectProperty:@selector(client) with:[_networkComponents httpClient]]; }]; }
... // Other UIAssembly components here
@end
Viola.
<br/>
###Activating
We can provide a different realization of the `NetworkComponents` or `PersistenceComponents` when [activating the assembly](Activating Assemblies) as long as the objects they build conform to the same class or protocol. This gives plenty of flexibility, for example, to patch out the network components for stubs in integration tests.
* If you declare your assemblies by concrete type, you can provide one that overrides that type (eg TestNetworkComponents for NetworkComponents).
* If you declare your collaborating assemblies as backed by a protocol you must instruct Typhoon what concrete realization to use on startup.
```objective-c
UIAssembly *uiAssembly = [[UIAssembly new]
activateWithCollaboratingAssemblies:@[
[TestNetworkComponents new],
[PersistenceComponents new]];
SignUpViewController* viewController = [uiAssembly signUpViewController];
##Layered architecture
Modules can really help to promote a neat, robust architecture. But without a little discipline its still easy to get things messed up. Try to avoid heavy coupling between your modules. A good rule-of-thumb is to aim for a layered architecture, as shown below. We have infrastructure components at the bottom, with increasing levels of abstraction until we reach our top-level application assembly. Visibility between the layers can point downwards, preferably to the layer immediately below, but generally should not point upwards. (There can be occasional exceptions to this, for example in the case of delegates).
###Summary
- While building the instantiating rules for your objects Typhoon always proxies collaborating assemblies.
- Which concrete classes will fill these roles is decided later. This allows substituting one assembly for another compatible one.
- During start-up Typhoon will collect the base classes of collaborating assemblies. You can supply another that overrides this. If you back your assemblies with protocols, you must supply the concrete types (
[assembly activateWithCollaboratingAssemblies:@[a, b, c]]
) at start-up.
See also: The The Typhoon Sample Application for an example showing the use of modularized assemblies.
See also: TyphoonPatcher, as described in Integration Testing is another approach to swapping out components for another implementation.
Something still not clear? How about posting a question on StackOverflow.
Get started in two minutes.
Get familiar with Typhoon.
- Types of Injections
- What can be Injected
- Auto-injection (Objective-C)
- Scopes
- Storyboards
- TyphoonLoadedView
- Activating Assemblies
Become a Typhoon expert.
For contributors or curious folks.