Creator pattern
This article publishes a draft of a previously unreleased object-oriented design pattern |
Please see also the UML diagrams of the Creator pattern on the next pages
The Creator design pattern provides a way to create objects of any generalized or concrete type, where the concrete type and the initialization of the created object is choosen by any arbitrary dynamic or static context and the client which invokes the creation has no dependency to the creating object (the factory) or its generalizations. The factory implements the instantiation and further initialization of the object to create, but is never called (directly) by the client itself. If the client invokes the creation of an object for a generalized type, then the client will have no knowledge about and no dependency to the concrete type of that object, which the factory uses to create that object.
The Creator pattern introduces creators. A creator is an object which determines and manages the creation of another object on behalf of the client which invoked the creator. A creator is of a concrete type and specialized for the type of the object that is to be created. Moreover, the creator can be specialized for a specific manner of construction for that type. All creators have at least one common generalization with a public "create" operation which implements the management of the object creation and optionally further related features such as an object life cycle management. A creator is typically instantiated by the client which invokes the "create" operation on that creator, but the creator could also be provided to that client (e.g. for a kind of prototyping). Either way, the client has a dependency to the creator, but no dependency to the factory.
Futhermore, whenever the creation of an object requires to pass arguments to that type, then the respective creator must incorporate these arguments by its own attributes and allow the client to set the respective values into the creator before calling the "create" operation. For instance, when the constructor (or initializing operation) of the concrete type that is to be instantiated requires certain arguments, then the creator must offer operations to set the values (setters) for these arguments to the client, and the creator must offer operations to get the values (getters) for these arguments to the factory which will use these arguments for the creation. Thus, the creator serves as well as a data transfer object between the client and the factory and has typically a similar set of attributes as the object that is created.
As stated initially, the creation of an object depends on a provided dynamic or static context, called creation context, not just on the type (actually the creator type) and the passed arguments. And it shall be possible to use any arbitrary object or set of values to represent that creation context. For instance a context may contain typical properties like system environment, subdomain, product category, product type, customer country, customer category etc., but may contain more dynamic properties like current date, current time, total amount, temperature, pressure, velocity, depth, flight level etc. as well. That means the Creator pattern realizes a multiple dispatch, even with dynamic contexts, on the factory methods used to create objects. Consequently, it requires a separate factory implementation for each separate creation algorithm for the same type of object. And each used creation context must be assigned to exactly one factory, while the same factory might be used in more than one context. So that contexts and factories are assigned in a many-to-one relationship from contexts to factories.
The Creator pattern includes a singleton, called the factory context, to compose the factories and the assignments from creation contexts to factories. Furthermore, the factory context provides operations to determine and get the factory for a certain context. In order to get a factory even if without passing a creation context to the factory context, the factory context is prepared with a default context that is passed to the factory context beforehand by any other components of the respective system. The factory context may also implement a fallback algorithm for the case no default context has been provided.
All factories have at least one common generalization with a public "create" operation which implements the default behavior for the case the concrete factory has no implementation for a certain object type. For instance, the default behavior may instantiate the object to create generically or throw an exception. The former reduces the number of factory operations which must be implemented by hand, the latter asserts no object will be created erroneously due to a missing implementation. The concrete factories provide overloaded siblings of that general "create" operation to implement their concrete creation algorithms. The general "create" operation takes a creator as argument with the generalized creator type, while the operations in the concrete factories take a creator as argument with the respective concrete creator type. Thus, a missing implementation in the concrete factory will cause the general "create" operation to be invoked.
Moreover, the design with overloaded factory operations, taking a creator as argument, enables the creator to call the factory passing itself as an argument. As a result, the call will be dispatched to the respective factory implementation as intended. This way the Creator pattern makes effectively use of a double dispatch. This means the factory operation is choosen by the type of the creator, which is choosen by the client (or passed by the client), and the concrete factory type is choosen by the creation context, which is choosen by the client (or any other component of the respective system) as well.
To create any object the client just needs to know which type of object shall be created (to create the respective creator) - and only in individual cases the client passes an individual creation context to the creator. The client only depends on the creator type and the type to create the object from. The creator type is just a sibling of that type to create the object from and resides in the same location or in a subpackage. So the dependency from the client to the creator type is just of a similar quality as the dependency from the client to the type to create the object from - and hence to be considered as harmless (in terms of invasiveness, removability, project structures and other impacts). The client is effectively decoupled from the rest of the creational framework, in particular the factories.
As a rough example for a possible sequence:
The client instantiates the creator and passes some necessary arguments to the creator. After the client called the "create" operation on the creator, but in this case without passing an individual creation context, the creator calls the factory context to get the default creation context. With that creation context the creator calls the factory context again to get the assigned factory. Then the creator calls that factory passing itself as argument. That call is dispatched to the respective operation in the factory where the implementation can access the concrete type of the creator to get the necessary arguments originally set by the client. With these arguments the factory creates the object as intended, but from an extension of that object type, and returns it to the creator finally returning it to the client. The client then uses the returned object as the intended type, not knowing the concrete type of that object.
The closest relative of the Creator pattern is the Abstract factory pattern, but the abstract factory pattern does not allow to choose the factory automatically by a context, does not allow for a generalized behavior, does not allow for a fallback behavior, the client needs some knowledge about the concrete factory (implementation) to choose the intended behavior, and that pattern creates a dependency from the client to the concrete factory. Moreover, it has been criticized that the abstract factory pattern creates higher efforts, when new object types are implemented and it has been criticized that it likely creates duplicate code. While this is true for the pure and simple variant of the abstract factory pattern, these issues can be solved by some simple amendments.
A reasoning and comparison with the dependency injection pattern will be discussed in a separate article.
Please note: Since the implementations of creators are just lightweight siblings of the implementations of the related object type, the implementations of creators can be generated from their siblings (e.g. with a template engine). It is strongly recommended to generate the creators as a generalization which shall not be altered by hand (overwritten on each generation) and as a concrete extension of that generalization which can be altered by hand if needed (just generated if missing).
Basic Navigator pattern
The following UML class diagram shows a basic variant of the Creator pattern:
The following UML sequence diagram shows selected interactions for the basic variant of the Navigator pattern:
Enhanced Navigator pattern
The following UML class diagram shows an enhanced variant of the Creator pattern, where creators and factories are divided into different domains or subdomains, e.g. modules:
More coming soon.
About the creator & author