|
|
Use
Interfaces and Reflection to Achieve Application Configurability
at the Class/Object Level
Over
the years I have seen a lot of questions from Java programmers
that have to do with choosing between different types of objects
based on some criteria, criteria that are not known at the time
the code is written or built but instead needs to be determined
at deploy-time or run-time. A recent example of this came up on
the Eclipse
newsgroups, where a programmer (and fellow Eclipse user) was asking
how he could have two different classes with the same name and
package in the same project. Apparently the application he was
working on needed to have two different implementations of this
class, one for "normal" serial operation and another
for parallel operation.
Well, there are various ways to accomplish having two different
versions of the same class (in fact, he had accomplished it using
Ant) , but doing so was masking the real issue - that this application
needed to make a choice between different implementations
at some time after the code was written and/or built. That
is where this little "mini-pattern" comes in.
[ Aside: Let me be clear about the word "pattern"
and how I use it here. What I'm talking about is vaguely related
to AbstractFactory (or FactoryMethod) and Strategy, though
it does not have to be as comprehensive as the GoF
definition of those Patterns. In other words, this
is more of a pattern than a Pattern.
]
The
idea is to use interfaces and Java reflection to allow your application
to instantiate certain classes based on runtime configuration,
instead of hard-coding instantiation of those classes.
Let's take a sample app that needs to use a Bar object. Let's
also say that there are many different kinds of bar (FooBar, BazBar,
etc.) and that all the different types of bar have the same public
interface. Finally, let's say that the app needs to use one of
those types of bars, but does not know at compile time which one
it might be.
The
solution involves the following steps:
- Define
an interface, called Bar, that contains the public methods
all bars must implement. For example:
public interface Bar { public void doSomething(); }
|
- Write
the various types of implementors of the Bar interface. For
example:
public class FooBar implements Bar { public void doSomething() { // do foo stuff here... } }
public class BazBar implements Bar { public void doSomething() { // do baz stuff here... } }
|
It does not matter what packages those impementations are
in or how they implement the Bar methods, just that they implement
the Bar interface.
- In
the client code (the code that needs to use a Bar), instead
of creating an instance like normal [such as new
FooBar()], determine from some configuration
mechanism what is the fully qualified name of the
Bar class that should be instantiated. The configuration mechanism
can be any number of things, depending on how your application
is deployed and the environment in which it is running. One
option is to use a System Property that defines the name of
the Bar implementation class; another is to read a config
file; a third is to get the class name from a database; a
fourth option is....well, you get the picture. It does not
matter how you choose to get the class name; what is important
is that somehow the code that needs Bar services can determine,
at runtime, the name of a class that implements the Bar interface.
- Once
the client code has determined which Bar impl class is desired,
use Java reflection to instantiate the class. For example:
Bar aBar; String barClassName = getBarImplClass(); aBar =
(Bar) Class.forName(barClassName).newInstance();
|
This
example is the simplest case, where the impl classes are assumed
to have no-argument constructors. The code gets a little more
involved if the constructor needs an argument/arguments (when
using reflection like this I usually tend to strive for no-arg
constructors and use a public initialize() method if data needs
to be passed to the instance to initialize it - that method can
be made part of the interface, which makes calling it easier once
you have a reference to a Bar instance).
|