Background
What is adapter (patterns) ?
Suppose you have a US-made laptop and you are now in Europe or China for a vacation. You may notice that the interface on the wall is incompatible for the plug you have for the laptop. Then you might need an adapter to “translate” or “convert” your plug interface.
This also applies to Software Development. A very common scenario is that. Customers or Clients will have a set of APIs to call or you used an external library to proceed the requests. But, the Backend system you build use another set of APIs with different arguments or endpoints. You wanna unify the gap betweens these two different sets of APIs. This is the time you wanna bring the “Adapter Pattern” onboard.
Meanwhile, you may also wanna check
- Facade Pattern
- Decorator Pattern
- Proxy Pattern
These patterns are pretty similar, so you may wanna check before designing the interface.
image credits to https://refactoring.guru/design-patterns/adapter
Adapter Pattern
The Adapter Pattern (a.k.a Wrapper) converts the interface of a class into another interface the clients expect. Adapter lets classes work together that couldn’t otherwise because of imcompatble interfaces.
Like what I mentioned in the background session. It is pretty common to see that the new things we wanna bring in conflict the existing code base. Of course, we developers are too lazy to modify / migrate the entire code base. So, we bring the “Adapter Pattern” to the play
Image credits to Head First: Design Patterns, by Eric Freeman, Elisabeth Robson, Bert Bates, Kathy Sierra
Blog: https://faun.pub/head-first-design-patterns-using-go-6-being-adaptive-the-adapter-and-facade-patterns-c6361a602c3a
The Client uses the Adapter:
- The client makes a request to the adapter by calling a method on it using the
Target
interface:ITarget
- The adapter translates the request into one or more calls on teh adaptee using the
adaptee
interface - The client receives the results of the call and never knows there is an adapter doing the translation
The Client and Adaptee are decoupled. neither do they knows about the other
Toy example: Duck and Turkey
Let’s take Duck and Turkey for an example of how Adaper Pattern works
Duck interface IDuck
:
public interface Duck {
public void quack();
publick void fly();
}
Here is a concrete class MallardDuck
which implements the Duck interface IDuck
public class MallardDuck implements Duck {
public void quack() {
System.out.println("Quack");
}
public void fly() {
System.out.println("I am flying!");
}
}
Now there is another interface Turkey ITurkey
and the class WildTurkey
which implements ITurkey
public interface Turkey {
public void gobble();
public void fly();
}
public class WildTurkey implements Turkey {
public void gobble() {
System.out.println("Gobble Gobble");
}
public void fly() {
System.out.println("I am flying very short distance");
}
}
Now the clients wanna use Duck objects but can only call on Turkey objects. So we create a TurkeyAdapter
class to adapt to the Duck class
/**
* implement the interface of the type you are adapting to
* since the client wanna see the `Duck` interface
* we create the `TurkeyAdapter` class to implement `IDuck`
*/
public class TurkeyAdapter implements Duck {
Turkey turkey;
/**
* Pass a reference to the object that we are adapting
*/
public TurkeyAdapter(Turkey turkey) {
this.turkey = turkey;
}
public void quack() {
turkey.gobble();
}
public void fly() {
for(int i = 0; i < 5; i++) {
turkey.fly();
}
}
}
After that we create a test class to do a simple testing
public class DuckTestDrive {
public static void main(String[] args) {
MallardDuck duck = new MallardDuck();
WildTurkey turkey = new WildTurkey();
Duck turkeyAdapter = new TurkeyAdapter(turkey); // wrap the turkey in TurkeyAdapter class
System.out.println("The Turkey says...");
turkey.gobble();
turkey.fly();
System.out.println("\nThe Duck says...");
testDuck(duck);
System.out.println("\nThe TurkeyAdapter says...");
testDuck(turkeyAdapter);
}
static void testDuck(Duck duck) {
duck.quack();
duck.fly();
}
}
Output:
$ java DuckTestDrive
The Turkey says...
Gobble gobble
I'm flying a short distance
The Duck says...
Quack
I'm flying
The TurkeyAdapter says...
Gobble gobble
I'm flying a short distance
I'm flying a short distance
I'm flying a short distance
I'm flying a short distance
I'm flying a short distance
Image credits to Head First: Design Patterns, by Eric Freeman, Elisabeth Robson, Bert Bates, Kathy Sierra
Blog https://fjp.at/design-patterns/adapter
- The
Client
is implemented against theTarget
interface - the
Adapter
implements theTarget
interface and holds an instance of theAdaptee
- In the above example,
TurkeyAdapter
implementIDuck
and holds an instance ofTurkey
- In the above example,
Object and Class Adapter
There are two types of adapters
- Object Adapter
- Class Adapter
We have already seen the Object Adapter in the above section. Let’s now check the Class Adapter
Image credits to Head First: Design Patterns, by Eric Freeman, Elisabeth Robson, Bert Bates, Kathy Sierra
Blog https://fjp.at/design-patterns/adapter
The only difference is that Class Adapter is the subclass of Target and Adaptee, instead of using composition to adapt the Adaptee.
Image credits to Head First: Design Patterns, by Eric Freeman, Elisabeth Robson, Bert Bates, Kathy Sierra
Real-world examples
Now let’s jump into a real-world example provided in the book: Iterators
and Enumerators
Iterator and Enumerator both let you step through the elements of a collection without knowing the specifics of how they are managed in the collection. But Iterator adds the ability to remove items
So to use adapter pattern, lets use the Enumerators
with code that expects Iterators
public interface Enumeration<E>
{
public bool hasMoreElements(); // Tells if there are any more elements in the collection
public E nextElement(); // Returns the next element in the collection/enumeration
}
In later versions of Java this was replaced with Iterators which has also a remove() method:
public interface Iterator<E>
{
public bool hasNext(); // Tells if there are any more elements in the collection
public E next(); // Returns the next element in the collection/iteration.
public void remove(); // Removes from the underlying collection the last element returned by this iterator
}
If you notice, Enumeration interface does not support remove()
method. What should we do?
public class EnumerationIterator implements Iterator<Object> {
Enumeration<?> enumeration;
public EnumerationIterator(Enumeration<?> enumeration) {
this.enumeration = enumeration;
}
public boolean hasNext() {
return enumeration.hasMoreElements();
}
public Object next() {
return enumeration.nextElement();
}
public void remove() {
throw new UnsupportedOperationException(); // we throw exception since remove() is not supported
}
}
References
- Head First: Design Patterns, by Eric Freeman, Elisabeth Robson, Bert Bates, Kathy Sierra
- Adapter Pattern - https://www.youtube.com/watch?v=2PKQtcJjYvc
- https://fjp.at/design-patterns/adapter
- https://faun.pub/head-first-design-patterns-using-go-6-being-adaptive-the-adapter-and-facade-patterns-c6361a602c3a