Adapter Design Pattern
What we've got here is... failure to communicate. Maybe there's a cool pattern that could give us a hand with that.
Introduction
You’re traveling internationally, and your electrical devices won’t work in your destination(s). You’ll need to take a set of electrical adapter plugs much like the ones shown above. You plug the adapter into the electrical outlet, and then you plug your device into the adapter. The adapter plug accommodates different outlet shapes as well as voltage, current frequency, and other differences.
The Adapter Design Pattern is much the same. It tends to be a relatively simple class that allows two classes to interact when their APIs don’t directly allow it.
Adapter continues the main theme of the Command, Strategy and Template Method design patterns – polymorphism. But Adapter sometimes adds another trick, delegation, or inheritance. Other design patterns use these, but Adapter uses them in a special way.
Adapter is about a change in the contract interface but not a change in behavior.
Structure
The previous design patterns, mentioned above, contain extending classes but for the most part, they aren’t the main features of those designs. Adapter features the extending classes showing how they use delegation or inheritance to bridge the communication gap.
Adapter Scenario
Before getting into the nuts-and-bolts, let’s start with a scenario. We start with a Target
interface that declares a request()
method.
We have a Client
that follows the design principle to Program to an interface, not an implementation, so it delegates to Target
.
If I were describing Command or Strategy, then the next diagram would add a concrete class extending from Target
that implements request()
, and we’d be done.
But in this scenario, the desired behavior has already been implemented in another class, Service
via a method called action()
. Now we have this:
We have a few problems. Command and Strategy are based upon a plug-in technique where the extending concrete class plugs into the interface. The extending concrete needs to know about and implement the interface method(s). But Service
doesn’t implement Target
. We can’t plug it in.
What are our options?
- We could have
Client
accessService
directly, but that would violate the first design principle. Additionally, there could be several other classes extendingTarget
thatClient
is already using. We cannot changeClient
. - We could change
Service
to extendTarget
and change its method name fromaction()
torequest()
, but that probably won’t work:Service
could be an external element, and we have no control over it.Service
could be an internal element already in use in the product. Changing it could break other code.Service
could be legacy code that’s used by the rest of the legacy code. No one wants to touch legacy code.
Two Adapter Design
Adapter can bridge this gap, and there are two variations of Adapter:
- Object Adapters
- Class Adapters
I think better terms would have been Composition Adapters and Inheritance Adapter respectively, but no one asked me. They compare and contrast in the same way that the Strategy and Template Method do. Both pairs of patterns solve their similar problems, but in different ways with one favoring composition and the other favoring inheritance. Neither is necessarily better or worse than the other. Both mechanisms have their own strengths and weaknesses.
Regardless of the technique, Adapter allows two classes to communicate when they are not designed to communicate. In our scenario, the Client
ultimately wants to access Service
, but Service
doesn’t fit in the Client
/Target
ecosystem. Adapters are translators. They translate the nomenclature of the Client
with the nomenclature of the Service
.
Object Adapter
The Object Adapter is based upon composition and delegation. Here are the basic elements:
Target
is the interface contract. It declaresrequest()
.- The
Client
has a reference to theTarget
interface, and it delegates to theTarget
’srequest()
method. TheClient
is programming to an interface, not an implementation. Adapter
implementsTarget
, and it must implementrequest()
. It has a reference toService
, and it implementsrequest()
with a one-line method that callsservice.action()
.
That’s it … mostly. The field attributes still need to be resolved. (See: Factory Method and Dependency Injection)
Class Adapter
The Class Adapter is based upon inheritance and tradition. Here are the basic elements:
Target
is the interface contract. It declaredrequest()
.- The
Client
has a reference to theTarget
interface, and it delegates to theTarget
’srequest()
method. TheClient
is programming to an interface, not an implementation. Adapter
implementsTarget
, and it must implementrequest()
.Adapter
extendsService
, and it implementsrequest()
with a one-line method that callsaction()
.
We still need to resolve a field attribute, but it’s not quite as much to set up as with the Object Adapter technique.
Both Adapter approaches work. I tend to prefer Object Adapters, but Class Adapters work as well. It depends upon whether you prefer composition or inheritance.
Pairs With Strategy
Adapter is rarely designed without it being paired with Strategy. Let me rephrase that. Adapter is Strategy. The only difference is that we have a little more implementation detail in Adapter in that it delegates to another class and translates API differences. Do a Google Image Search for Adapter Design Pattern UML class diagram and you’ll see the telltale signs for Strategy as well.
Summary
The Adapter Design Pattern allows one class to access another class even when their APIs differ. However, the adapted class needs to be similar enough in intent so that using an Adapter makes sense. Adapters might need additional implementation to translate argument types and return types as well, which was not shown in the diagrams here.
The Adapter Design Pattern does one more thing, which I haven’t mentioned yet. It helps keep the implementation modular and loosely coupled.
The Client
/Target
code still doesn’t know about the Service
code and vice versa even after being bridged by the Adapter
.
If Service
changes its contract interface, then an Adapter based design may be able to absorb the impact of the interface change in the Adapter
without any change to the Client
/Target
code.
Adapter implementations tend to be small. Each method is usually only a few lines long.
References
There are many online resources with diagrams and implementations in different programming languages. Here are some free resources:
- Wikipedia Adapter Design Pattern
- Source Making Adapter Design Pattern
- Refactoring Guru Adapter Design Pattern
- DoFactory Adapter Design Pattern
- Project Management Institute Adapter Design Pattern
- and for more, Google: Adapter Design Pattern
Here are some resources that can be purchased or are included in a subscription service:
- Gang of Four Adapter Design Pattern
- Agile Principles, Patterns, and Practices in C#, Chapter 33 (O’Reilly and Amazon)
- Clean Code: Design Patterns, Episode 34 video (Clean Coders and O’Reilly)
- Head First Design Patterns, Chapter 7 (O’Reilly and Amazon)