The Whiteboard Pattern is a design pattern used in
OSGi applications. It has been published 2004 as an alternative to the Observer Pattern (see the
whitepaper of the OSGi alliance.)
Since 2004 - the era of Java 1.4 - Java has received many improvements. So the implementation of the Whiteboard Pattern is much simpler nowadays.
The use case
Assume we have a class
NumberProducer that calculates numbers (e.g. prime numbers or something similar).
NumberProducer is used in different environments with different interfaces to present the numbers to the user.
It is also possible that there are multiple components available that can print numbers.
So an interface
NumberPrinter is defined which specifies the interface the
NumberProducer uses to send the calculated numbers to the printing class:
public interface NumberPrinter {
String printNumber(long value);
String getLabel();
}
Finding a implementation of NumberPrinter
But how does the
NumberProducer find an implementation of
NumberPrinter when it has numbers to be printed?
This is exactly what the whiteboard pattern descibes.
In this example we have 3 actors:
- The NumberProducer
- Two different implementations of NumberPrinter
To use the whiteboard pattern we need an additional component: the whiteboard (which is implemented by the OSGi registry).
When a bundle that provides an implementation of
NumberPrinter is activated, it registers this implementation at the OSGi registry. (This is comparable to sticking a note on the whiteboard.)
When the
NumberProducer has numbers to print, it searches the OSGi registry for
NumberPrinters ...
.. and calls the
printNumber() method on each of them:
Implementation part 1: Implement and register a NumberPrinter
When using
OSGi declarative services annotations it's quite simple to register a
NumberPrinter at the OSGi registry:
Just implement the interface and add the
@Component annotation to your class:
@Component
public class HexPrinter implements NumberPrinter {
@Override
public String printNumber(long value) {
return Long.toHexString(value);
}
@Override
public String getLabel() {
return "Hex";
}
}
Hint: DS annotations are meant to be processed by the IDE or building toolchain. Use e.g.
Declarative Services Annotations Support Eclipse Plug-In or
BndTools for this.
Implementation part 2: Let the NumberProducer find and use NumberPrinters
The OSGi Framework provides the
BundleTracker that helps finding services that implement specific interfaces.
I decorated this class with a method that takes a closure and applies it to all matching services (see
complete code at GitHub):
...
public void forAllServices(Consumer<S> func) {
S[] services = getServicesAsArray();
for (S service : services) {
func.accept(service);
}
}
...
Using this helper class makes the implementation of the
NumberProducer simple (
complete code):
public class NumberProducer {
private void printNumberOnPrinters(long number,
WhiteboardAccess<NumberPrinter> whiteboardAccess) {
whiteboardAccess.forAllServices((srv) -> {
System.out.println(srv.printNumber(number));
});
}
...
That's it
As we saw, the implementation of the whiteboard pattern is fast, simple and nearly fail-safe when we're using modern technologies.
Links