The way objects are constructed, retrieved and isolated is an important decision to take when developing any non-trivial Flex application. This article takes a step back, explains how objects can be constructed and retrieved and why and when they need to be isolated. Additionally, this article explains how this motivates dependency injection and the recent popularity of IoC frameworks that often provide additional benefits around messaging and object lifecylce management. However, don't depend on an IoC framework. This article attempts to explain the motivations and provides options how IoC can be achieved without an IoC framework if required.
A PM constructs a domain object using the "new" operator followed by a hard-coded dependency.
public
function
MyPM()
{
domain =
new
MyDomain();
}
While this works in isolation, if another part of the application wants to know about the state of the domain object it has no direct way to retrieve it.
The hard-coded dependency of the domain object inside the PM binds the presentation layer strongly to a specific implementation of the domain. An interface of the domain would allow looser coupling and using messaging, the developer could even completely decouple architectural layers.
The implementation of the domain object cannot be changed without changing the implementation of the PM, which can constrain the flexibility of the application.
The domain object takes a higher level meaning to the application as the PM. It should be higher level objects that control the implementation of lower level objects such as the PM as the other way around. When the domain would be abstracted and controllable via higher level objects, this process can be called inversion of control as opposed to the usual, naive approach of design where lower level objects control all other objects.
Additionally to the motivation of the application to change the implementation of the domain, a unit test could have a variety of motivations to do the same, explained in the paper Agile Unit Testing.
The conclusion is that it is cumbersome for the PM to substitute the domain with a different implementation when the PM constructs the domain in itself. It is also very cumbersome to retrieve the same instance of the domain anywhere else in the application. Below is a list of options that allow to separate object construction of object behaviour often with the removal of the "new" operator and the hard coded dependency. This separation allows for greater flexibility in design and testing, however, some options have other negative consequences, which need to be understood before choosing them.
Following traversal the domain model would be instantiated in the highest, common view or PM. The domain instance would then be passed through hierarchies of views or PMs. The advantage of following this approach is that the domain can be injected into every receiving object. This can ease isolation of the receiving object and provide design and test benefits if the type is an interface or common base type. One disadvantage is that some views or PMs need to know about the traversed model object just because they are in a higher hierarchy than the two dependent views or PMs. Higher level PMs can become very difficult to isolate because they can contain many dependencies. Refactorings can become more involved because a higher number of objects is likely to be affected.
One form of locating an object is to make the same object locatable by itself. The Singleton pattern is one approach that allows for this. A static accessor, created for each model, holds the instance in question.
Injection of depended-on objects rather than locating them has advantages in unit testing. Therefore, developers can improve their design when injecting the instances located via the Singleton approach into lower level objects, instead of locating the instance repeatedly within the object. Within the object that has to use a Singleton directly, a developer could not just substitute the Singleton with a different implementation for design or test motivations because the Singleton by design will always return the same instance. However, a developer could move the Singleton access into a protected method that returns its type as an interface. Doing this allows unit tests to create test subclasses that override and substitute objects returned by Singletons with a test double.
One possible refactoring away from the Singleton approach is to separate the responsibility of object construction from the object domain completely. With following this approach, the static accessor that creates and stores the instance is a dedicated object. The other object is solely responsible for its own problem domain.
Combined with the dependency injection, hierarchies of factories can be formed and create an application domain model increasingly unaware of the object construction model via factories. One drawback of this approach is the additional need to create factory objects.
Instead of writing retrieval code per model object (see Singleton and Static Factory approaches), a global repository allows for a single repository that takes on this responsibility. A global repository frees all other parent objects from having to implement retrieval logic.
ModelLocator of the Cairngorm 2 framework is a global Singleton that contains linkages to all of its children and is therefore type safe. Objects managed by ModelLocator can only be substituted for i.e. unit tests with overwriting them while they are typed as interfaces. One drawback of using the ModelLocator is that it can expose references to top level models when nested models need to be retrieved.
A Registry is a hashmap (Dictionary or Object) that contains dynamic linkages to all of its children and is therefore not type safe. Objects managed by Registries are easily substitutable by overwriting them with test doubles. Registries do not expose model hierarchies such as the ModelLocator.
IoC frameworks take on the responsibility of object construction. The developer is required to specify their concrete objects in the IoC container.
Later, the IoC framework can provide these concrete objects by dependency injection or dependency lookup. If the object that needs access to a concrete object defined in the IoC container is also defined in the IoC container, the IoC framework can inject the depended-on object. Otherwise, the IoC framework can be used as a global repository; a registry. IoC frameworks save developers from writing factories for the models they want to retrieve.
Because the IoC framework takes on such a great control of your objects, it can also add other benefits over construction, retrieval and isolation of objects.
IoC frameworks can provide a messaging system without handlers to know about their event source (Swiz's Mediate, Parsley's MessageHandler).
IoC frameworks can manage the life cycle of objects with calling initialization and disposal methods upon construction and deconstruction.
While various described options can offer additional flexibility in design and testing via inversion of control, no option achieves the ease of dependency injection and additional benefits that IoC frameworks achieve with an improved messaging framework and the management of object life cycles. Also, dependency injection has proven to be the easiest substitution mechanism for unit testing and IoC frameworks support this by nature.
Evans, E. (2004). Domain-Driven Design: tackling complexity in the heart of software. Addison-Wesley.
Martin, R. The Dependency Inversion Principle. http://www.objectmentor.com/resources/articles/dip.pdf of OOD principles .