1. Overview

In this tutorial, we'll look into the use cases of Adapter Pattern and how to implement it in Java.

2. When to Implement

Sometimes a class can't be reused only because its interface doesn't match the domain-specific interface.

Let's assume that we have an interface for counting animals, AnimalCounter with the countAnimal method. Then there came a time when we need to integrate with another party. They have a similar interface and functionality, Counter with the count method. However, we can't directly use it because our system is set up according to AnimalCounter.

Another example can be the Enumeration and Iterator interfaces from JDK. The early versions of Java introduced Enumeration, but now Iterator is the recommended class for iteration purposes.

In the end, to work with both interfaces and their implementations, we can use the Adapter Pattern. The adapter will serve as the middle man, performing adaptation between different interface methods.

3. How to Implement

We'll first define two class hierarchies that provide similar functionality.

Firstly, we have the Duck interface for simulating ducks:

public interface Duck {

    void quack();

    void fly();
}
public class MallardDuck implements Duck {

    @Override
    public void quack() {
        System.out.println("Quack quack...");
    }

    @Override
    public void fly() {
        System.out.println("Fly...");
    }
}

The Duck interface has quack and fly methods.

Secondly, we have the Turkey interface which has similar methods as Duck:

public interface Turkey {

    void gobble();

    void fly();
}
public class WildTurkey implements Turkey {

    @Override
    public void gobble() {
        System.out.println("Gobble gobble...");
    }

    @Override
    public void fly() {
        System.out.println("Short distance flight...");
    }
}

However, it has the gobble method instead of quack. They both have the fly method, but the behaviors may differ.

So if we want to use the Turkey instances as Duck, we must create an adapter. The adapter will behave as Duck and will use a Turkey instance behind the scenes:

public class TurkeyAdapter implements Duck {

    private final Turkey turkey;

    public TurkeyAdapter(Turkey turkey) {
        this.turkey = turkey;
    }

    @Override
    public void quack() {
        turkey.gobble();
    }

    @Override
    public void fly() {
        for (int i = 0; i < 5; i++) {
            turkey.fly();
        }
    }
}

Here, TurkeyAdapter implements the Duck interface, because we want to treat the Turkey objects as Duck. TurkeyAdapter also gets a reference to a backing Turkey object. This way, the adapter can forward calls to this object. For delegation purposes, TurkeyAdapter calls the fly method of Turkey five times. For the quack method, it just delegates to gobble.

Finally, let's see the client code:

public class ClientMain {

    public static void main(String[] args) {
        Duck duck = new MallardDuck();
        Turkey turkey = new WildTurkey();
        
        Duck turkeyAdapter = new TurkeyAdapter(turkey);
        System.out.println("----Turkey");
        turkey.gobble();
        turkey.fly();
        
        System.out.println("----Duck");
        testIt(duck);
        
        System.out.println("----Turkey Adapter");
        testIt(turkeyAdapter);
    }

    public static void testIt(Duck duck) {
        duck.quack();
        duck.fly();
    }
}

When we run the client, we can see the adapter in action:

----Turkey
Gobble gobble...
Short distance flight...
----Duck
Quack quack...
Fly...
----Turkey Adapter
Gobble gobble...
Short distance flight...
Short distance flight...
Short distance flight...
Short distance flight...
Short distance flight...

4. Summary

In this tutorial, we've investigated the Adapter Pattern and looked at its implementation in Java.

Finally, check out the source code for all examples over on Github.