Components are Good!
Spire and Duke is plugging in... |
Another related problem I have come across several times, is the use of Singletons, used to hold various implementations. To be fair, I have created more than a couple of those singletons myself, but nowadays, I almost never use them.
In this post, I will show why and how to avoid them and at the same time gain some additional advantages like separation of interfaces and implementations using Components and a
ComponentHandler
.The
ComponentHandler
that is shown in the examples a bit further down is derived from the open-source project Speedment that I am contributing to. Speedment is great if you want to write database applications in Java 8 using standard Java 8 Streams. Read more about Speedment here.Why Are Singletons Bad?
The purpose of a singleton is to guarantee that just a single instance of a class or interface is present at any single time. This is of course not a bad thing in it self. On the contrary, there are many situations when we want this property, for example for logging or for other system components like queues or factories. However, the way people chose to enforce the singleton property is more often than not, simply bad.In my previous post Enforcing Java Singletons is Very Hard, I presented a number of Singleton Idioms that can be used to enforce the singleton property and in the same post, I showed that it is hard to really enforce the singleton patterns (albeit "cheating" with reflections) and also talked about some other disadvantages of singletons.
Here is one of the idioms from the post, called the Enum Singleton Idiom which I first learned about in Joshua Bloch's book "Effective Java" many years ago.
public enum FooEnumSingleton {
INSTANCE;
public static FooEnumSingleton getInstance() { return INSTANCE; }
public void bar() { }
}
This provides a relatively solid and simple singleton, but there are some drawbacks. Because all enums inherit from the built-in Java class
Enum
, we can not inherit from other base classes. Furthermore, we can not override methods when we want to test our singleton in JUnit tests, for example if we want to "harnes" our class, call some methods and then inspect its state or functionality. Additionally, there is no support or control over the singletons Lifecycle Management. When we first reference the singleton above, the class loader will create and initialize it, pretty much outside our control. If another programmer calls our singleton, it will load no matter what. Now, if our singleton depends on other singletons, which each depends on yet another singleton and so on, our code will likely break. Perhaps not in the first version where things were easy, but it is just a matter of time until it undoubtedly will fail.I will talk more about lifecycles in the next section.
Component Lifecycles
The Lifecycle of a component or class can be certain stages the component will pass during the time it exists. For example, a component may be created, added, started, stopped and and then eventually, removed from a system. Using the right tools, a component can easily be setup to react to its own lifecycle events.The Component Handler
Often, it is very convenient to setup some kind of Component Handler that keeps track of all our singleton components. This way, we can have them all collected at one place and treat them in a consistent way. The Component Handler can also be made to automatically call the component's lifecycle methods whenever they are added, changed or removed from the Component Handler.Now, do not fall for the temptation to make the Component Handler a singleton under the excuse that "now it is just one singleton". Admittedly, it is better to have just one singleton than having 100 singletons, but it is generally even better to just let our Component Handler be just a normal object and keep track of the Component Handler instance in our code. Especially if we are deploying our application in a web server, where there might be many instances of the "Singletons" under different class loaders.
The Class Mapper
The classical (apologies for the bad word-joke) "Class Mapper" is a good starting point when we want to implement a Component Handler. Take a look at this example:public interface ClassMapper {
/**
* Puts a new mapping and returns the previous mapping for the given class,
* or null if no mapping existed.
*
* @param <K> key class
* @param <V> item type
* @param keyClass to use
* @param item to add
* @return the previous mapping that existed, or null
* if no mapping existed
*/
<K, V extends K> K put(Class<K> keyClass, V item);
/**
* Returns the mapping for the given class, or null if no mapping exists
*
* @param <K> The class type
* @param clazz the class to use
* @return the mapping for the given class, or null if no mapping exists
*/
public <K> K get(Class<K> clazz);
}
public class ClassMapperImpl implements ClassMapper {
private final Map<Class<?>, Object> map;
public ClassMapperImpl() {
this.map = new ConcurrentHashMap<>();
}
@Override
public <K, V extends K> K put(Class<K> keyClass, V item) {
requireNonNull(keyClass);
requireNonNull(item);
@SuppressWarnings("unchecked")
// We know that it is a safe cast
// because V extends K
final K result = (K) map.put(keyClass, item);
return result;
}
@Override
public <K> K get(Class<K> clazz) {
@SuppressWarnings("unchecked")
// We know that it is a safe cast
// because the previous put()
// guarantees this
final K result = (K) map.get(clazz);
return result;
}
}
The good thing with this is that now we have a small rudimentary Component Handler. Suppose that we have an interface like this:
public interface JokeComponent {The
String nextJoke();
}
nextJoke()
method is supposed to return a funny joke, and we want a pluggable concept so we are planning to implement several versions of the JokeComponent
. We start by constructing a very simple JokeComponent
that just returns a single joke regardless how many times we call the nextJoke()
method:public class StaticJokeComponent implements JokeComponent {Now we can test our Component Handler as shown in the following program:
@Override
public String nextJoke() {
return "I went to buy some camouflage trousers the other day,"
+ " but I couldn’t find any.";
}
}
public class Main {
public static void main(String[] args) {
final ClassMapper componentHandler = new ClassMapperImpl();
setupComponents(componentHandler);
crankOutTenJokes(componentHandler);
}
private static void setupComponents(ClassMapper componentHandler) {
// Plug in the selected basic JokeGenerator
componentHandler.put(JokeComponent.class, new StaticJokeComponent());
}
private static void crankOutTenJokes(ClassMapper componentHandler) {
// Get the current JokeGenerator that is plugged into the componentHandler
JokeComponent jokeGenerator = componentHandler.get(JokeComponent.class);
// Tell the world who is making the jokes
System.out.println(jokeGenerator.getClass().getSimpleName() + " says:");
// print ten of its joke
IntStream.rangeClosed(1, 10)
.mapToObj(i -> jokeGenerator.nextJoke())
.forEach(System.out::println);
}
}
This will produce the following jokes:
StaticJokeComponent says:Even though the joke might be a bit fun the first or second time, we start to lose interest a bit further down the list... Time for improvement by writing a new
I went to buy some camouflage trousers the other day, but I couldn’t find any.
I went to buy some camouflage trousers the other day, but I couldn’t find any.
I went to buy some camouflage trousers the other day, but I couldn’t find any.
I went to buy some camouflage trousers the other day, but I couldn’t find any.
I went to buy some camouflage trousers the other day, but I couldn’t find any.
I went to buy some camouflage trousers the other day, but I couldn’t find any.
I went to buy some camouflage trousers the other day, but I couldn’t find any.
I went to buy some camouflage trousers the other day, but I couldn’t find any.
I went to buy some camouflage trousers the other day, but I couldn’t find any.
I went to buy some camouflage trousers the other day, but I couldn’t find any.
JokeComponent
that doesn't tell the same joke all the time.public class RandomJokeComponent implements JokeComponent {Now we can plug in this
private final Random random = new Random();
private final List<String> jokes = Arrays.asList(
"What's blue and doesn't weigh much? Light blue.",
"What do you get when you cross a joke with a rhetorical question?",
"What do you call a fish with no eyes? A fsh.",
"Two men walk into a bar... the third one ducks.",
"A farmer rounded up her 196 cows and got 200 of them."
);
@Override
public String nextJoke() {
return jokes.get(random.nextInt(jokes.size()));
}
}
JokeComponent
instead and see what happens. Modify the code so it looks like this instead:private static void setupComponents(ClassMapper componentHandler) {Now we get another output:
// Plug in the selected basic JokeGenerator
// Use the RandomJokeGenerator this time instead ---+
// V
componentHandler.put(JokeComponent.class, new RandomJokeComponent());
}
What's blue and doesn't weigh much? Light blue.This is a small step forward for the humor society, but we could as well write yet another implementation of the
Two men walk into a bar... the third one ducks.
A farmer rounded up her 196 cows and got 200 of them.
What do you call a fish with no eyes? A fsh.
What do you get when you cross a joke with a rhetorical question?
A farmer rounded up her 196 cows and got 200 of them.
What do you get when you cross a joke with a rhetorical question?
What do you call a fish with no eyes? A fsh.
What's blue and doesn't weigh much? Light blue.
What do you call a fish with no eyes? A fsh.
JokeComponent
that, whenever nextJoke()
is called, goes out on the internet and retrieves a funny story from some site. The user code outside the component would remain exactly the same.Note that the Component Handler can handle any number of interfaces and the interfaces can look completely different and contain different methods, so you could set it up to also handle
RidleComponent
and perhaps also BlooperComponent
that could, for instance, return URL:s to funny films on the net.The ComponentHandler with Lifecycles
It is fairly easy to expand the Component Handler so it can handle Component Lifecycles. Let us define a simple Lifecycle Component like this.
public interface Component {So, every time we add a
void added();
void removed();
}
Component
to the Component Handler, we want the Component Handler to automatically invoke the Component::add
method and every time it is removed we want it to call the Component::removed
method. That way, we know that all component in the Component Handler will be initialized (by the added()
method) properly and also that they will clean up stuff (if needed by the removed()
method) when they are removed from the Component Handler. Evidently, every component that we want to add to the Component Handler must implement the Component interface. First, we improve the ClassMapper
interface so it takes a generic parameter for the values to extend.public interface ClassMapper<T> {
<K extends T, V extends K> K put(Class<K> keyClass, V item);
public <K extends T> K get(Class<K> clazz);
}
Then we could write a
ComponentHandler
as depicted here under:public class ComponentHandler implements ClassMapper<Component> {
private final Map<Class<? extends Component>, Component> map;
public ComponentHandler() {
this.map = new ConcurrentHashMap<>();
}
@Override
public <K extends Component, V extends K> K put(Class<K> keyClass, V item) {
requireNonNull(keyClass);
requireNonNull(item);
item.added();
@SuppressWarnings("unchecked")
// We know that it is a safe cast
// because V extends K
final K result = (K) map.put(keyClass, item);
if (result != null) {
result.removed();
}
return result;
}
@Override
public <K extends Component> K get(Class<K> clazz) {
@SuppressWarnings("unchecked")
// We know that it is a safe cast
// because the previous put()
// guarantees this
final K result = (K) map.get(clazz);
return result;
}
}
I have made some simplifications in the code, but the general outline becomes obvious. Whenever we add a
Component
, its added()
method will be called and whenever we replace a component with a new one, the old's removed()
method is called.In the example below, we are to make a
Queue
for Strings that is a pluggable Component
in the new ComponentHandler
we just wrote. We could define a StringQueueComponent
as a Component
like this:public interface StringQueueComponent extends Component {We could start with a very straightforward implementation using one of the existing
boolean offer(String msg);
String poll();
}
Queue
implementation already in the Java framework.public class MemoryStringQueueComponent implements StringQueueComponent {
private final Queue<String> queue = new ConcurrentLinkedQueue<>();
@Override
public boolean offer(String msg) {
return queue.offer(msg);
}
@Override
public String poll() {
return queue.poll();
}
@Override
public void added() {
System.out.println(getClass().getSimpleName() + " added");
}
@Override
public void removed() {
queue.clear();
System.out.println(getClass().getSimpleName() + " removed");
}
}
and then we may come up with an alternative
Queue
that places the strings on a file as shown in this outline (I have not shown the actual code for file handling because it is quit bulky, but the principle is obvious).public class FileStringQueueComponent implements StringQueueComponent {
// File variable goes here
@Override
public boolean offer(String msg) {
// Append the string to the file
}
@Override
public String poll() {
// Check if the file has grown
// if yes, read the next line and return it
// if no, return null
}
@Override
public void added() {
// Open the file and trunkate it if contains an old queue.
// Set a pointer to the current file location
}
@Override
public void removed() {
// Close the file and remove it from the file system
}
}
ComponentHandler
to use as depicted below:public class Main {it will produce the following output (given a real implementation of the
public static void main(String[] args) {
ComponentHandler componentHandler = new ComponentHandler();
setupComponents(componentHandler);
///
putStuffInTheStingQueue(componentHandler);
printStuffInTheStringQueue(componentHandler);
// Select a new queue component (content of the old queue is lost)
componentHandler.put(StringQueueComponent.class, new FileStringQueueComponent());
putStuffInTheStingQueue(componentHandler);
printStuffInTheStringQueue(componentHandler);
}
private static void putStuffInTheStingQueue(ComponentHandler componentHandler) {
StringQueueComponent queue = componentHandler.get(StringQueueComponent.class);
queue.offer("A");
queue.offer("B");
queue.offer("C");
}
private static void printStuffInTheStringQueue(ComponentHandler componentHandler) {
StringQueueComponent queue = componentHandler.get(StringQueueComponent.class);
System.out.println("Stuff in " + queue.getClass().getSimpleName());
String s;
while ((s = queue.poll()) != null) {
System.out.println(s);
}
}
private static void setupComponents(ComponentHandler componentHandler) {
componentHandler.put(StringQueueComponent.class, new MemoryStringQueueComponent());
}
}
FileStringQueueComponent
):MemoryStringQueueComponent added
Stuff in MemoryStringQueueComponent
A
B
C
FileStringQueueComponent added
MemoryStringQueueComponent removed
Stuff in FileStringQueueComponent
A
B
C
Conclusion
By using a Component Framework, we gain many advantages over singletons :
- We can control the lifecycle of our components.
- The ability to test our components becomes much better.
- We get a clear separation between interfaces an implementations.
- We collect all our component in one place and access them in a standardized way.
- We can ensure that our component is not called before it is initialized.
- Our code will be less likely to fail in a multi-classloader scenario like a web server.
If you want to take a look at a real existing component handler, have a look at Speedment's component handler on GitHub. For performance reasons, this component handler is more optimized so that we can obtain components without the overhead of looking them up in a
Map
.