Adapter Pattern
Classic GOF Structural Design Patterns
Intent
Adapter pattern works as a bridge between two incompatible interfaces. This pattern involves a single class which is responsible to join functionalities of independent or incompatible interfaces.
It is used when you need to:
- Convert the interface of a class into another interface that the clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.
- Wrap an existing class with a new interface to make it appear to be some other type.
- Reduce impedance mismatch to allow a component to work in the context of a new system
Problem
Two classes or services (components) need to be able to communicate; but have no common method or interface to do so. The Adapter Pattern allows the two to communicate by acting as a bridge between the two.
Discussion
The real world is filled with adapters - they surround us everyday. The device you are reading this on requires an adapter to even work. Phones and Tablets require a low voltage to charge and work and yet the wall socket you plug them into everyday is 120 volts. The power adapter has a simple job, to change the 120V in the outlet to something much smaller to run your device. The adapter doesn't do anything else, just this one simple job - adapting your device to run from the electrical outlet.
The key objective of the Adapter Pattern is to do the exact same thing between two mismatched components in software. As an example, typically a client expects a specific interface when making a call. If the component that it is calling is updated to something new (one with different methods or different protocols), it won't work without updating every client. An adapter sits between the client and caller and implements the interface that the client expects. In effect, it is "pretending" to be the target that the client expects; but, is able to interact with the new component.
The Adapter Pattern is about creating an intermediary abstraction that translates, or maps, an old component to a new system (or vice versa). Clients call methods on the Adapter object which redirects them into calls to the target component. This strategy can be implemented either with inheritance or with aggregation.
As stated earlier, the Adapter functions as a wrapper or modifier of an existing class. It provides a different or translated view of that class.
Structure
Structurally, Adapters come in two basic flavors or approaches. If you do some research on the adapter pattern, you will find two different implementations of it:
- The class adapter pattern that implements the adapter using inheritance.
- The object adapter pattern that uses composition to reference an instance of the wrapped class within the adapter.
Composition provides more flexibility and avoids unexpected side-effect when you need to change existing code. Due to this, the composition-based adapter pattern is by far the more popular approach and the one I will focus on here.
As you can see in the diagram above, the client is totally unaware that it is using an adapter to communicate with the target component. The components here are fairly basic:
- Client - the component that makes the call. It expects a target that accepts an interface that it knows about.
- Expected Interface - the interface that the caller knows about and expects to be able to use.
- Adapter - a custom component that implements the expected interface and knows how to translate incoming calls to interact with the target
- Target - the component that the caller needs to communicate with but can't without the adapter.
Practical Example
For a practical example of the Adapter Pattern in a real world usage scenario, lets imagine that we have a service that interacts with a third party system outside our control. It could be a system for billing or even sending out emails - its purpose doesn't really matter for our example, only that it isn't something that we have control over.
Now imagine that the provider of this third party service decides that they are going to update their service (this never happens, right?) and add some security. While they are adding these new features, they change up some of the methods that they expose and you discover that your services that use it will now be broken and no longer work. A similar scenario also plays out when you use third party components and they are "upgraded" - but they don't keep consistency with their previous interfaces.
Much like the generic diagram in STRUCTURE above, anything that previously communicated with the old version of the third party service will no longer work. The new version of this service has been improved by adding security tokens to the calls we were previously using. Because of this change, we have two options: first, to update every client that uses the third party service, or second, to add an adapter in front of the third party service so that our existing code doesn't have to be changed.
Let's extend this sample with some practical code below.
Code Samples
Below, you will find examples of the Adapter implemented in four different commonly used languages. Each one of the examples implements the exact same scenario as described in the PRACTICAL EXAMPLE section above.
Each sample will use an Adapter to allow communication between a legacy client and the "new and improved" third party service.
C# Adapter Code Sample
In this C# example, we implement a simple Adapter to allow communication between a legacy client and a "new and improved" Third Party Mailer Service.
Java Adapter Code Sample
In this Java example, the Adapter pattern is used to provide communication between a legacy client and a "new and improved" Third Party Mailing Service.
Typescript Adapter Code Sample
In this TypeScript example, the Adapter pattern is used to provide communication between a legacy client and a "new and improved" Third Party Mailing Service.
JavaScript Adapter Code Sample
In this JavaScript example, the Adapter pattern is used to provide communication between a legacy client and a "new and improved" Third Party Mailing Service.
Considerations
- Identify the players: the component(s) that want to be accommodated (i.e. the client), and the component that needs to adapt (i.e. the target).
- Identify the interface that the client requires.
- Design a "wrapper" class that can "impedance match" the adaptee to the client.
- The adapter/wrapper class "has a" instance of the adaptee class.
- The adapter/wrapper class "maps" the client interface to the adaptee interface.
- The client uses (is coupled to) the new interface.
Additional Notes
- All requests are forwarded, so there is a slight increase in the overhead.
- Sometimes many adaptations are required along an adapter chain to reach the type which is required.
- Adapter makes things work after they're designed; Bridge makes them work before they are.
- Bridge is designed up-front to let the abstraction and the implementation vary independently. Adapter is retrofitted to make unrelated classes work together.
- Adapter provides a different interface to its subject. Proxy provides the same interface. Decorator provides an enhanced interface.
- Adapter is meant to change the interface of an existing object. Decorator enhances another object without changing its interface. Decorator is thus more transparent to the application than an adapter is. As a consequence, Decorator supports recursive composition, which isn't possible with pure Adapters.
- Facade defines a new interface, whereas Adapter reuses an old interface. Remember that Adapter makes two existing interfaces work together as opposed to defining an entirely new one.
- One advantage of the Adapter Pattern is that you don’t need to change the existing class or interface. By introducing a new class, which acts as an adapter between the interface and the class, you avoid any changes to the existing code.