Loosely Coupled Components, Part 1
I have mentioned often that one of the major keys to building good software
components is independence… that is, having code that doesn't have
dependencies on other code. This is all great in theory, but as soon as you
have a component make a function call to another component, you have just
created a dependency.
Of course you are now saying, how would you build an application if you can't
hook components together… Function calls are only one way of integrating
components together, we call this approach tight coupling.
There are places where tight coupling is fine. For instance, if you create
a servlet that makes function calls and object instantiations from one or
more components, you are creating a dependency where the servlet depends on
these components. But servlets are generally application specific, and
therefore not normally reusable, so such a dependency is usually acceptable.
However, if you have to get data from one component to another, then your
servlet should get the data from the first component and pass it to the
other component… don't get tempted to have the component pass the data
directly, otherwise, you create an unnecessary dependency (see the
following illustration).
But what if you wanted to make your servlet (or other caller) independent?
This is where we get into the realm we call loose coupling. The idea here
is to instantiate and call components such that replacing the component doesn't
require a recompile of the caller… or at least, not require the source code to
be modified.
Think of a "plugin" where putting some code is a particular location and
maybe editing a configuration file is all it takes to increase an
application's features.
The two most popular techniques for this is using Reflection and Aspect
Oriented Programming (AOP). In a feeble attempt at keeping these articles
short, I'm going to break this up, and discuss using Java interfaces, and
then later talk more about Reflection and AOP in follow-up articles…
Interfaces
—
We have a few layers in loose coupling where some techniques are looser
than others. The first level is using Interfaces, where instead of
instantiating and calling an object directly, you use a Builder (design
pattern) to instantiate an object that adheres to an interface, and then
only call methods that are part of that interface. Let's have an example,
shall we?
Let's pretend like we have a useful interface that defines a single method
called "square" that computes the square of a number we give it. Something
like this:
public interface PowerEngine {
public long square (long value);
}
Now you need a factory that can determine (based on reasons unknown to the
client) which class that implements this interface to instantiate and
return to the client. It looks sort of like this:
public class PowerEngineFactory {
public static PowerEngine getEngine() {
// Do some calculations and then
return new PowerEngineA();
}
}
A client would then use this code via:
PowerEngine eng = PowerEngineFactory.getEngine();
long power = eng.square(54);
What have we gained by this exercise? Well, it means that we can change an
implementation of PowerEngine
(even changing the name) without
having to update the client. We also could have multiple implementations
and have the factory figure out which would be the best response (often by
reading at runtime a configuration file).
We have all used these sorts of factories as the Java API is full of them.
In order to have a standard XML parsing library and yet allow anyone to
implement a new version, we ask for an XML parser from a factory:
include javax.xml.parsers.*;
SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
In this case, every XML parsing library you may download and install
implements this runtime factory. You can change a library implementation
without altering your source code.
But doesn't a client now depend on the factory class, i.e.
PowerEngineFactory
? Yes it does. It also depends on access to
the PowerEngine
interface code. Granted, the factory classes
usually don't change as much as the classes they return, but we still have
more dependencies than we might like.
Before I talk about a more general solution using Reflection, let me
clarify something:
You don't have to actually create an interface. You could create an
abstract parent and have all of the implementing classes simply derive from
this parent. Now your client will be expect an object of the parent class.
However, I think it is clearer as to your intentions to create an
interface.
Tell others about this article: