Introduction
The Adapter pattern is used to make one interface compatible with another. It allows objects with different, and therefore incompatible interface to work together.
If you for example want to bring an existing class into your system with an interface that doesn’t match any existing interface, and you can not modify this class, you write an adapter class to bridge the gap.
It looks like this:
The pattern consists of different parts:
- The client has an object which implements the Subject interface.
- The object in question is the Adapter.
- The implementation of the operation() method in the Adapter wraps the concrete_operation in the RealSubject class.
That way we can talk to the RealSubject class using an already known interface. The Adapter class takes care of converting our method-calls for the RealSubject. Also, because this pattern is loosely coupled, changing the RealSubject should be relatively painless.
What is the difference between the Decorator and the Adapter pattern?
Decorator | Adapter | |
Purpose | The decorator pattern is used to add behaviour to objects dynamically without modifying the source. | Make one interface compatible with another |
Use | This pattern is used to when you want to add or extend behaviour to existing classes in a flexible and reusable way without creating a hierarchy of subclasses. | Used to integrate an existing class with an interface which is not compatible with existing interfaces. Modifying this class is not needed if you wrap it in an Adapter. |
Structure | Creating a decorator pattern involves creating a set of decorator classes that implement the same interface as the component, thereby extending or modifying its behaviour | Create an Adapter class, implementing the desired interface. This class also holds an instance of the class you want to adapted, the adaptee. The adapter delegates calls to the adaptee converting or transforming the input or output of these calls. |
Example | Imagine having a bakery, you add things like GlazingDecorator or a WhippedCreamDecorator wrapping around your Pie-class to produces pies of the desired kind | A classic example would be the conversion of units of measurement. If you have a classes which expects lengths to be in feet and inches, yet your client needs meters, you can wrap the classes in ConverterAdapter and have the conversion done automatically |
In short, Adapter is used to make incompatible things work together, while the Decorator adds and extends functionality.
Implementation in Go
We will start with usual preliminaries:
package main
import (
"fmt"
"strconv"
)
A short explanation what we will build:
- We have an existing class which doubles numbers, which takes an int16 as a parameter
- However our client produces numbers as strings.
This is a very trivial example on purpose, but it touches on the important points of this pattern.
We will start with the Subject interface, that is the interface the client sees:
type Subject interface {
Request(number string) string
}
This interface is self-explanatory
Next we define the ConcreteSubject struct. This is an empty struct because in this example, the ConcreteSubject does not hold any state:
type ConcreteSubject struct {
}
func (a *ConcreteSubject) SpecificRequest(number int16) int16 {
return number * 2
}
Here we implement the SpecificRequest() method, which takes and returns an int16. This is the implementation of the new class, and we need to adapter to use this interface.
The Adapter struct looks like this:
type Adapter struct {
ConcreteSubject
}
func (adapter *Adapter) Request(number string) string {
n, _ := strconv.Atoi(number)
result := fmt.Sprintf("%d", adapter.SpecificRequest(int16(n)))
return result
}
A few notes:
- With the use of Go embedding, we embed the ConcreteSubject.
- Next we implement the Subject interface. The conversion between the two interfaces is done here, and the method delegates the call to Request() to SpecificRequest
Time to test
Before we can test, we need an extra function:
func clientCode(subject Subject) {
fmt.Println(subject.Request("7"))
}
This is the simulation of the client-block in our diagram.
The main function looks like this:
func main() {
subject := ConcreteSubject{}
adapter := Adapter{subject}
clientCode(&adapter)
}
Line by line:
- We construct an empty ConcreteSubject struct
- We pass that to the constructor of the adapter.
- In clientCode() we call the Request() method on the subject. The Adapter will handle this, since it implements the Subject interface.
Conclusion
This pattern can be quite confusing at first, at least that is what I found. The main points of this pattern are:
- You wrap the class with the incompatible or new interface in the Adapter class.
- Delegate method-calls where necessary to the ‘adaptee’ class, and make sure you convert both the input and output parameters where needed.
Once I realized this, implementing this pattern was quite easy. I realize that implementing this pattern in existing systems and legacy code might be a bit more work.