Creator - examples
Here you can find some simplified examples, how to use the Creator framework.
Using the Creator framework to create objects context dependent is normally as simple as to type something like
Foobar foobar = new FoobarCreator().create();
(using a default context)
or
Foobar foobar = new FoobarCreator(foo).create();
(using parameters and a default context)
or
Foobar foobar = new FoobarCreator().create(localContext);
(using a per-creation context)
or
FoobarCreator foobarCreator = new FoobarCreator().setContext(localContext);
Foobar foobar = foobarCreator.create();
(using a per-creator context)
But before we can create objects that easily and with any context we like, we have to implement some creational infrastructure that is specialized for our system and domain.
We will do that on the following pages...
Let's say we have a client class where we want to create an object of the type Foobar. And Foobar is an abstract class with 2 concrete extensions: ConcreteFoobar1 and ConcreteFoobar2. Note: for the purposes of this example, Foobar might represent whatever type of object we want: an entity, business object or data transfer object as well as a "stateless" service, data access object, etc.
public abstract class Foobar {
private Foo foo;
public Foobar(Foo foo) {
this.foo = foo;
}
public Foo getFoo() {
return this.foo;
}
public void doFoobarStuff() {
// generalized foobar behavior
}
}
The 2 concrete extensions just override the method doFoobarStuff() in order to alter the bahavior.
First of all we need to implement an abstract factory - so we can use that factory type already to implement the creator, which will depend on it.
public abstract class Domain1Factory extends GenericFactory {
abstract public Foobar create(FoobarCreator creator);
}
Note: this will not compile yet, because the FoobarCreator is not yet existing (we implement the FoobarCreator next). If that worries you, just create an empty stub of the FoobarCreator first or add the factory method just after the FoobarCreator is implemented.
The factories implement overloaded "create" methods. The extended factory (here GenericFactory) implements a default behavior for the case an implementation is missing in a concrete factory (here automatic object creation, even with arguments).
Note: Our system might consist of segregated subdomains or modules etc. and therefore employ different abstract factory types to support the segregation. That's why we implement that abstract extra level ("Domain1"). That is not necessary, but recommended for larger systems.
Corresponding to the abstract Domain1Factory we implement an abstract Domain1Creator.
public abstract class Domain1Creator<T> extends Creator<T, Domain1Factory> {}
Now we can implement the Creator. Even if we opt for an automatic generation of the Creator classes, we should always know how to implement them by hand. Anyway it's a piece of cake!
public class FoobarCreator extends Domain1Creator<Foobar> {
private Foo foo;
public Foobar(Foo foo) {
this.foo = foo;
}
public Foo getFoo() {
return this.foo;
}
@Override
public Class<Foobar> getType() {
return Foobar.class;
}
@Override
public Foobar create(Domain1Factory factory) {
return factory.create(this);
}
}
As you can see we just copied the field, constructor and getter from Foobar. Then we extend the abstract Domain1Creator providing as generic parameter the type we create objects from.
We added to the creator one method that simply returns the type we create objects for and one method to perform the actual creation of objects. The latter performs a dispatch on the overloaded factory method.
Now it's time to implement the concrete factories. One factory for the behavior of ConcreteFoobar1 and one factory for the behavior of ConcreteFoobar2.
public class ConcreteFactory1 extends Domain1Factory {
@Override
public Foobar create(FoobarCreator creator) {
return new ConcreteFoobar1(creator.getFoo());
}
}
public class ConcreteFactory2 extends Domain1Factory {
@Override
public Foobar create(FoobarCreator creator) {
return new ConcreteFoobar2(creator.getFoo());
}
}
We want the (behavior) implementation of a Foobar, either ConcreteFoobar1 or ConcreteFoobar2, to be choosen by a context, which is the main reason to use a creational framework or creational pattern after all.
The context can be technical e.g. represents if the current process is running in a test environment or in a production environment, so either a test implementation or a production implementation will be choosen, but the context can also be domain specific, to dispatch the behavior by domain specific criteria. For purposes of this example we choose the following context properties:
- environment: "DEV", "TEST", "PROD".
- product type: "BAR1", "BAR2".
- customer category: "GOLD", "SILVER", "BRONZE".
and for the extra thrill a dynamic property:
- current month: 0-11 . (foobar comes in seasonal flavors
)
This Creator framework promises that you can use any arbitrary object or set of values as context. That is true, but under one condition: the context object must implement the marker interface CreationContext:
public interface CreationContext<F extends Factory> {}
(Sure, the framework could work fine without that marker interface, but on the long run this interface will rescue the maintainability of the project in terms of finding each and every context used...)
Note: the CreationContext is bound to a factory type. Remember we use abstract extra levels of factories and creators to represent segregated areas. For congruence with these areas we set that upper bound accordingly.
Now, we could create or modify a class to implement the marker interface with the context properties. Fortunately, the Creator framework provides already very convenient ways to define a creation context with the following classes:
- GenericCreationContext (takes any set of given parameters, which will be evaluated equally)
- RankingCreationContext (takes any set of given parameters, which will be evaluated by their rank - first parameter wins over the second, the second over the third etc.)
More CreationContext types can be derived from that, but for our example we just implement the following context:
CreationContext<Domain1Factory> context = new GenericCreationContext<>(
getEnvironment(),
getProductType(),
getCustomerCategory(),
getCurrentMonth()
);
But wait... where shall we implement that? Well, we really do not want to implement that in every client class and we also try to avoid to pass it to every client class. We rather implement it once in an appropriate place of our system where the context will be updated on the respective events, e.g. when the user changes the product type, and from there we set the context on each update as default context for a creator type (here Domain1Creator) into the FactoryContext. This way the Creator framework will always pick the right context even if the client itself has no knowledge about it! We might use instead a context that updates automatically by the use of references and set that context just once as the default context. This implementation depends completely on the architecture of our system.
The default context can be set on updates like this:
FactoryContext.getInstance().setDefaultContext(context, Domain1Creator.class);
For convenience, we can pass more than one creator type to that method.
We still have to define on which exact criteria, means context state, a certain factory shall be choosen by the framework. So we need to assign to each factory at least one concrete context state or vice versa we assign exactly one factory to each separate and relevant context state. Normally we need to do this just once without updates.
The ConcreteFactory1 shall be choosen if the following state is given:
CreationContext<Domain1Factory> context = new GenericCreationContext<>(
"PROD",
"BAR1",
null,
new Month() {
@override
public boolean equals(Object obj) {
// true if obj has value 3-8
}
}
);
Note: this context state shall match for certain months only. it's a summer product
We can initialize the factory and assign it to that context like this:
FactoryContext.getInstance().addContext(context, new ConcreteFactory1());
We do the same for the ConcreteFactory2:
CreationContext<Domain1Factory> context = new GenericCreationContext<>(
"PROD",
"BAR2",
null,
null,
);
FactoryContext.getInstance().addContext(context, new ConcreteFactory2());
Alternatively we could do this for one context / factory together with the setting of the default context in one step:
FactoryContext.getInstance().addDefaultContext(context, new ConcreteFactory1(), Domain1Creator.class);
If nothing important has been forgotton, then the object creation should work now as specified initially!
Foobar foobar = new FoobarCreator().create();
Let's assume the default context got updated to this state:
- environment: "PROD".
- product type: "BAR1".
- customer category: "GOLD".
- current month: 6 .
Then the returned Foobar will be an instance of ConcreteFoobar1.
And with this state:
- environment: "PROD".
- product type: "BAR2".
- customer category: "GOLD".
- current month: 6 .
The returned Foobar will be an instance of ConcreteFoobar2.
But what about this state:
- environment: "PROD".
- product type: "BAR1".
- customer category: "GOLD".
- current month: 1 .
An exception will be thrown because no registered context matches with that state. It is not allowed to access a product of type "BAR1" in the second month of a year, as we specified before.
The created Foobar object will be created in the default scope (life cycle) "CREATION_REQUEST". Thus the lifetime of that object will depend solely on the client side and not be maintained by the framework and each creation call will create a new instance. But for certain purposes we need other scopes, e.g. a session scope, a singleton scope etc. Please have a look at the interface CreationScopeIdentifier to see the an enumeration of scopes in the Creator framework - currently 12 scopes. Of course we can also define custom scopes. Alternatively, scopes can be nested in each other for an easy custom scope definition.
Scopes can be applied to an object creation very simple within the responsible factory (typically in the default constructor):
addScope(Foobar.class, CREATION_CONTEXT);
With that, all objects of the type Foobar created in that factory will live in the "CREATION_CONTEXT" scope, that is one instance (of that type) per creation context.
To integrate the Creator framework (with a server specific Creator extension) with JEE web servers, in order to manage the life cycle of objects scoped to web requests and web sessions, we just add a single entry to the JEE deployment descriptor (usually ../WEB-INF/web.xml). We add the ServletContextListener:
<listener>
<listener-class>net.swdes.creator.jee.ServletContextListener</listener-class>
</listener>
That's all. The Creator framework will do the rest automatically to manage the life cycle of objects scoped to web requests and web sessions.
Furthermore we might want to apply filters to the creation process in order to add a cross cutting behavior, proxies etc. to all creations or by criteria. Even more specific, we might want to preprocess or postprocess a certain creation with a convenient access to the object, method, arguments, context and scope. The Creator framework provides for that AOP-like capabilities, but in pure Java. Have a look at the interfaces CallPreprocessor and CallPostprocessor.
Note: the AOP wording is not used in that area - on purpose
This was just a short overview of some simple stuff you can do with the Creator framework - so much more to discover...
More coming soon.
About the creator & author