The Adapter Pattern
Adapter Pattern
Contextual Forces
Motivation
There are two primary reasons to use an adapter:
- To use the behavior of an existing object, using a different interface than it was designed with.
- To make an existing object exchangeable with a polymorphic set of objects.
The Adapter is generally used when one or both of these motivations exists, but the existing object cannot be changed.
Encapsulation
The Adapter encapsulates the different-ness of the object being adapted. It hides the fact that the adaptee has a different interface, and also that it is of a different abstract type (or is purely concrete, and has no abstract type).
Also, since the client would deal with a non-adapted (local) object and an adapted (foreign) object in the same way, the fact that the client is using one object in the first case and two in the second case, is also encapsulated. This is a limited form of the encapsulation of cardinality (see the Decorator Pattern).
Case 1: Local Implementation, one object used by the client
Case 2: Foreign Implementation with Adapter, two objects used by the client
We also note that the client in Case 1 is interacting with a single object, whereas in Case 2 is is effective interacting with 2 objects. The code in the client is the same, and so we would consider this distinction ("cardinality") to have been encapsulated as well by the use of this pattern.
Procedural Analog
A method, function, or other code segment that redirects behavior to another method, function, or code segment, hiding this redirection from the consuming code:
rval m(para p) { c = (cast)p; x = n(c); return (cast)x; }
Non-Software Analog
I travel a lot, and so my primary computing device is a laptop. I notice that even though laptop batteries have improved dramatically (lithium-ion or nickel-mental-hydride as opposed to nickel-cadmium), still they seem to quickly lose their ability to hold a charge for very long.
As a result, when I am at a coffee shop, airport terminal gate, or hotel lobby, I am always "on the hunt" for a wall outlet to plug my power brick into. Unfortunately, I am rarely the only person on this hunt, and typically someone else will have found the one outlet, and is "hogging" it.
My solution is to look for a lamp. A lamp has what I need -- 110 volts at 60 cycles, paid for by somebody else :). It does not present it in the form that I need it, however (two little slots that my power brick plug will fit into), but in a form that is appropriate for a light bulb (a threaded socket and a copper tab in the bottom).
So, I carry one of these in my laptop bag:
I (quietly) unscrew the light bulb from the lamp, screw in my adapter, and plug my laptop into it.
My laptop was designed to a specific interface. This one:
All wall sockets like this one (all across the United States) are exchangeable for one another, from my laptop's point of view. In a sense, they are a polymorphic set. The lamp socket is not exchangeable because its interface is not acceptable, even though the "stuff it has to offer" is just what I need.
The adapter allows my laptop to consume the electricity the way it was designed to, and makes the lamp socket interchangeable with the wall sockets of the world.
Implementation Forces
Example
public class TargetAbstraction { public abstract ret m(par p); } public class Adapter extends TargetAbstraction { private ForeignClass myForeignClass(); public Adapter() { myForeignClass = new ForeignClass(); //ForeignClass.getInstance() might be better } public ret m(par p) { var y = myForeignClass.n((cast)p); return (cast)y; } }
Questions, concerns, credibility checks
- How large is the delta (difference) between the interface that the foreign class offers and the interface of the target abstraction? If it is very large, this may be difficult to accomplish with a simple adapter. The Façade Pattern should be considered in this case.
- If the foreign class throws exceptions, should the adapter re-throw them directly, re-throw them as a different exception, or deal with them itself?
- How will the adapter obtain the instance of the foreign class? Direct instantiation (shown in the code) is actually not preferable, as it violates the encapsulation of construction principle; but the foreign class may not have been designed with this principle in mind. If this is the case, we may need to create an object factory to encapsulate the instantiation.
- Will the adapter need to add functionality to the foreign class? In other words, is the foreign class feature-poor to some degree?
- Will the adapter need to be stateful?
Options in implementation
The form shown above uses delegation from the adapter to the foreign class to reuse the pre-existing behavior. Another form of the adapter (called the class adapter) uses inheritance instead:
This implementation is more common with languages that support multiple inheritance, but is also possible in single-inheritance languages if the Target Abstraction is achieved through the use of an Interface type.
Also, if the purpose of the adapter is to add methods to an existing interface, in .Net 3.5 one can use the extension method mechanism to accomplish this:
public interface TargetInterface { Result Foo(int i, string s, char c, object o); } public static class TargetInterfaceAdapter{ public static Result Foo(this MyInterface target, string s){ return target.Foo(-1, s, 'X', null); } public static Result Foo(this MyInterface target, string s, char c){ return target.Foo(-1, s, c, null); } } public static class Main { public static void Bar(TargetInterface my) { //full call var r0 = my.Foo(123, "abc", 'c', new object()); //call through the adapter, using an added method var r1 = my.Foo("abc", 'c'); } }
(thanks to Alexander Fedin for this idea and example)
Consequent Forces
Testing issues
To test the adapter, you can use a Mock object in place of the foreign object (which would normally be adapted). The Mock can return predictable behavior for the adapter to convert, and also can record the action the adapter takes with the adaptee if this is deemed an appropriate issue to test.
Cost-Benefit (gain-loss)
The adapter is a very low-cost solution, and is therefore quite commonplace. The cost is the creation of an additional class, but the benefits are:
- Encapsulated reuse of existing behavior
- Polymorphism (through an up-cast) with a foreign class
- Promotes the Open-Closed Principle
- If the construction of the foreign class was not encapsulated (which is common), the adapter can encapsulate it in its constructor. However, an object factory is preferred.