The service provider interface pattern

Today I’d like to talk about a not so well known design pattern. It is actually so little known, that people sometimes are using it without even knowing that they are implementing a pattern. (I was no exception. Until my friend Andrea has drawn my attention that this thing had a name, I had used it several times without even noticing it – I thought it simply was some flexible design).

What

Suppose you have a group of objects that are doing similar pieces of work – they are validating data, converting between data structures, transforming objects etc- but with slightly different input data. The question is: how do you select one of your objects at runtime (as a response to some external event) such that you have to do as less extra work as possible, when a new validator/converter/translator gets added to the system?

Let’s consider a very simple example: given a JMS client that can accept different types of messages from a queue; create a method that is capable of dispatching the messages to different message handlers.  A very naive solution would be a bunch of if-else or switch-case statements. Such an implementation would be something like this:

    @Override
    public void onMessage(Message message) {
        Result result = null;
        if (message instanceof TextMessage) {
            result = textMessageHandler.handle((TextMessage)message);
        } else if (message instanceof MapMessage) {
            result = mapMessageHandler.handle((MapMessage)message);
        } else if (message instanceof ObjectMessage) {
           result = objectMessageHandler.handle((ObjectMessage)message);
        }
        ...
    }

This would certainly work, but:

  1. it’s ugly
  2. every message type needs a new else-if statement
  3. every message type needs to be referenced by the class that contains the onMessage method
  4. it’s rather procedural than object-oriented
  5. and ugly, again

How

Well if it’s ugly, then it’s time to refactor. It’s easy to see the message handlers all carry out a very similar task: they interpret one incoming message. Let then object oriented best practices rock, and have all the message interpreters implement a common interface; say Handler.

public interface Handler {
    Result handle(Message message);
}

public class TextMessageHandler implements Handler {
    @Override
    public Result handle(Message message) {
        //...
    }
    //...
}

public class MapMessageHandler implements Handler {
    @Override
    public Result handle(Message message) {
        //...
    }
    //...
}

Now in the message listener class we can have a cleaner implementation for the onMessage method:

@Override
    public void onMessage(Message message) {
        Result result = correctHandler.handle(message); //bear with me
        //...
    }

It looks somewhat better.  Two questions are, however, still open. One: how do we get a hold of the correctHandler, and two: how do we know if a handler is correct for a specific message type?

As we said earlier, we like object oriented concepts. One such OO idiom is encapsulation: data and methods working on data put together in a class. Why couldn’t then a specific handler tell, whether it can handle a message or not? With a tiny change to the interface, we can achieve just that. Consider this:

public interface Handler {
    Result handle(Message message);
    boolean canHandle(Message message);
}

public class TextMessageHandler implements Handler {
    @Override
    public Result handle(Message message) {
        //...
    }
    //...
    @Override
    public boolean canHandle(Message message) {
        return message instanceof TextMessage;
    }
}

And now, with the smarter handlers it’s easy to answer the other open question about how to obtain a correct message handler: have an entity, a supplier that can select the correct handler. Let that be a kind of repository for all the available handlers and let it pick the correct implementation (based on the canHandle() method):

public class Handlers {
    List<Handler> handlers;

    public Handlers(List<Handler> handlers) {
        this.handlers = handlers;
    }

    public Result handle(Message message) {
        for (Handler handler:handlers) {
            if (handle.canHandle(message)) {
                return handler.handle(message);
            }
        }
    return new EmptyResult();
    }
}

So we can change the onMessage() method to this:

@Override
    public void onMessage(Message message) {
        Result result = handlerRepository.handle(message);
        //...
    }

And pretty much that’s it. Now the onMessage method does not have to know about any specific message handler – it only knows about the repository. Should a new message handler type come into play, there is only one place you have to change – the initialization logic for the list of Handlers – and you are all set. If you happen to use Spring, you don’t even have to change anything; you just create the new handler class, and let Spring take care of the rest for you (remember that Spring is able to provide you with a list of all the beans that implement a particular interface. Exactly, write one single class and you are done).

Why

In the example above, we’ve used SPI to decouple business objects from event handling logic. The onMessage() method can now deal with accepting and acknowledging messages, while it does not know anything about the object that will eventually process the message. The bean only sees the repository object, and has no knowledge of the interface Handler, or any of its implementers.

Things that can change (the concrete handlers) have been separated from dispatching logic, so any change in the message handling (the removal of a message handler, for instance) would not affect the message reception or message dispatching. It is now easier to test this component, as you don’t have to engage in extensive mocking.

Single responsibility principle is satisfied; the message listener deals with listening to messages, the dispatcher routes the messages to the correct handler, while a handler’s only responsibility is to correctly interpret the incoming message.

Advertisements

Author: tamasgyorfi

Senior software engineer, certified enterprise architect and certified Scrum master. Feel free to connect on Twitter: @tamasgyorfi

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s