Adapter design pattern in java

In this article I’ll introduce the Adapter Design Pattern and how you can use it in Java. This pattern is mostly used to enable loosely-coupling of plugins into applications (for example the way Eclipse does this).

About the pattern

Let’s see what the Gang of Four (or GoF) tell us about this pattern:

“Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces.”

So as you can see the adapter pattern comes handy if you want to adapt functionality to work between incompatible types.

In this case the adapter has to be a “man in the middle” which converts the request of the source to the request accepted by the target. Naturally you can have bidirectional communication too, in this case the adapter converts the result from the target service to the one which is accepted by the source.

Downsides?

Yes, there are downsides too. Using this pattern gives more complexity to your code which makes errors harder to debug. This is why some say that this pattern is just a fix for a badly designed system — but if we take a step back and look at our system landscape we have the right to use this pattern to communicate with unchangeable legacy systems.

How to use the pattern?

To use the pattern we have to do some coding where we adapt code-base to work with another code-base.

If you have the two sides of the communication identify what your source sends and what the target accepts. If you identified this you can create the adapter in the middle which accepts the source’s call and maps it to the target’s expectation.

Example

Imagine we have a very important customer who has two systems which generate and work on data respectfully and this customer wants us to create an app which gathers data from one system and sends it to the other. We only know the interfaces which we can use — so we have to adapt the result of the first system to be a valid input of the second.

As I mentioned: the Adapter Pattern comes to rescue. The example I provide connects two libraries and converts the information between these two to fit the output into the input.

Adapting to libraries

In this first example we only have the interfaces which are the contracts of both systems:

This is the data-source system’s interface:

/**
 * This is the data provider contract. We call this to gather our data and trigger the second library to work.
 *
 * @author GHajba
 *
 */
public interface DataProvider {
    /**
     * Gets some data which is provided by the system somehow.
     *
     * @return a DTO containing the resulting information -- can be null.
     */
    public FirstSystemDTO getData();
}

This is the DTO which is returned

/**
 * The DTO to transfer the results of some data gathering to the callers.
 *
 * @author GHajba
 *
 */
public abstract class FirstSystemDTO {
    /**
     * @return the first name value -- can be null
     */
    public abstract String getFirstName();

    /**
     * @return the middle name value -- can be null
     */
    public abstract String getMiddleName();

    /**
     * @return the last name value -- can be null
     */
    public abstract String getLastName();
}

And here is the information consumer:

/**
 * This is the second system's contract which we want to call after the data is gathered from the first system.
 *
 * @author GHajba
 *
 */
public interface DataConsumer {
    /**
     * Triggers some work based on the parameter in the system.
     *
     * @param name
     *            the work is based on this parameter
     *
     * @throws NullPointerException
     *             if name is null
     */
    void triggerWorker(String name);
}

As you can see, this second system does not know what to do with a FirstSystemDTO and this DTO does not have a convenience method which returns the whole name. So a method call like the following is out of the question:

dataConsumerInstance.triggerWork(dataProviderInstance.getData());

This would be too nice. Beside this the first system is very lazy, it can return null everywhere which is not too good. Anyway we create the application and we need a solution to map between the two systems. So our code has to be the adapter:

public class Adapter {

    DataProvider dataProviderInstance = new DataProviderInstance();
    DataConsumer dataConsumerInstance = new DataConsumerInstance();

    /**
     * This method is the adapter which gets the data from the provider and triggers the consumer.
     */
    public void doOurWork() {
        final FirstSystemDTO data = this.dataProviderInstance.getData();
        this.dataConsumerInstance.triggerWorker(dataToString(data));
    }

    /*
     * This method converts the gathered information into a null-safe String
     */
    private String dataToString(FirstSystemDTO data) {
        if (data == null) {
            return "";
        }
        return join(" ", data.getFirstName(), data.getMiddleName(), data.getLastName());
    }
}

As you can see it is not very hard to create an adapter for two libraries, the only thing you need is some code to write. Naturally this example was very trivial and sometimes you need to write more code to map requirements between two interfaces.

The join function concatenates a list of strings with a given string delimiter. The first parameter is the delimiter, the others are varargs for the strings to concatenate.

Adapting to services

Adapting to external services is almost the same than adapting to interfaces. Sometimes you have transfer objects (DTOs) to accept and send, sometimes you need to call the services directly with some parameters.

A good use-case for this would be a web-service where you have an external system which provides a DTO to communicate with but you need to convert this DTO into a format which you can accept.

Conclusion

The Adapter pattern is useful to solve communication / protocol problems between systems. So use this pattern only if the system is designed and in production or you have a third-party solution where you cannot change the codebase to be right. While you are developing software look at the Bridge pattern.

4 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.