This chapter covers the adapter pattern.
GoF Definition
Convert the interface of a class into another interface that clients expect. Adapter lets classes work together that could not otherwise because of incompatible interfaces.
Concept
The core concept is best described by the following examples.
Real-World Example
A very common use of this pattern can be seen in an electrical outlet adapter/AC power adapter in international travels. These adapters act as a middleman when an electronic device (let’s say, a laptop) that accepts a US power supply can be plugged into a European power outlet. Consider another example. Suppose that you need to charge your mobile phone, but you see that the switchboard is not compatible with your charger. In this case, you may need to use an adapter. Or, a translator who is translating language for someone can be considered following this pattern in real life.
Now you can imagine a situation where you need to plug in an application into an adapter (which is X-shaped in this example) to use the intended interface. Without using this adapter, you cannot properly join the application and the interface.
Computer-World Example
Suppose that you have an application that can be broadly classified into two parts: user interface (UI or front end) and database (back end). Through the user interface, clients can pass a specific type of data or objects. Your database is compatible with those objects and can store them smoothly. Over a period of time, you may feel that you need to upgrade your software to make your clients happy. So, you may want to allow new type of objects to pass through the UI. But in this case, the first resistance comes from your database because it cannot store these new types of objects. In such a situation, you can use an adapter that takes care of the conversion of the new objects to a compatible form that your old database can accept.
Note
In Java, you can consider the java.io.InputStreamReader class and the java.io.OutputStreamWriter class as examples of object adapters. They adapt an existing InputStream/OutputStream object to a Reader/Writer interface. You will learn about class adapters and object adapters shortly.
Illustration
A simple use of this pattern is described in the following example.
In this example, you can easily calculate the area of a rectangle. If you notice the Calculator class and its getArea() method , you understand that you need to supply a rectangle object in the getArea() method to calculate the area of the rectangle. Now suppose that you want to calculate the area of a triangle, but your constraint is that you want to get the area of it through the getArea()method of the Calculator class. So how can you achieve that?
To deal with this type of problem, I made CalculatorAdapter for the Triangle class and passed a triangle in its getArea() method. In turn, the method treats the triangle like a rectangle and calculates the area from the getArea() method of the Calculator class.
Class Diagram
Package Explorer View
Implementation
Output
Modified Illustration
You have just seen a very simple example of the adapter design pattern. But if you want to strictly follow object-oriented design principles, you may want to modify the implementation because you have learned that instead of using concrete classes, you should always prefer to use interfaces. So, keeping this key principle in mind, let’s modify the implementation.
Modified Class Diagram
Key Characteristics of the Modified Implementation
The Rectangle class is implementing RectInterface and the calculateAreaOfRectangle() method helps calculate the area of a rectangle object.
The Triangle class implements TriInterface and the calculateAreaOfTriangle() method helps calculate the area of a triangle object.
But the constraint is that you need to calculate the area of the triangle using the RectInterface ( or, you can simply say that your existing system needs to adapt the triangle objects). To serve this purpose, I introduced an adapter(TriangleAdapter), which interacts with the RectInterface interface.
Neither the rectangle nor the triangle code needs to change. You are simply using the adapter because it implements the RectInterface interface, and using a RectInterface method, you can easily compute the area of a triangle. This is because I am overriding the interface method to delegate to the corresponding method of the class (Triangle) that I am adapting from.
Notice that getArea(RectInterface) method does not know that through TriangleAdapter, it is actually processing a Triangle object instead of a Rectangle object.
Notice another important fact and usage. Suppose that in a specific case, you need to play with some rectangle objects that have an area of 200 square units, but you do not have a sufficient number of such objects. But you notice that you have triangle objects whose area are 100 square units. So, using this pattern, you can adapt some of those triangle objects. How? Well, if you look carefully, you find that when using the adapter’s calculateAreaOfRectangle() method, you are actually invoking calculateAreaOfTriangle() of a Triangle object ( i.e., you are delegating the corresponding method of the class you are adapting from). So, you can modify (override) the method body as you need (e.g., in this case, you could multiply the triangle area by 2.0 to get an area of 200 square units (just like a rectangle object with length 20 units and breadth 10 units).
This technique can help you in a scenario where you may need to deal with objects that are not exactly same but are very similar. In the last part of the client code, I have shown such a usage where the application displays current objects in the system using an enhanced for loop (which was introduced in Java 5.0).
Note
In the context of the last point, you must agree that you should not make an attempt to convert a circle to a rectangle (or similar type of conversion) to get an area because they are totally different. But in this example, I am talking about triangles and rectangles because they have some similarities and the areas can be computed easily with minor changes.
Modified Package Explorer View
Modified Implementation
Modified Output
Types of Adapters
GoF explains two types of adapters: class adapters and object adapters.
Object Adapters
In our example, TriangleAdapter is the adapter that implements the RectInterface (Target interface). Triangle is the Adaptee interface. The adapter holds the adaptee instance.
Note
So, if you follow the body of the TriangleAdapter class, you can conclude that to create an object adapter, you need to follow these general guidelines:
(1) Your class needs to implement the target interface (adapting to interface). If the target is an abstract class, you need to extend it.
(2) Mention the class that you are adapting from in the constructor and store a reference to it in an instance variable.
(3) Override the interface methods to delegate the corresponding methods of the class you are adapting from.
Class Adapters
Class adapters adapt through subclassing. They are the promoters of multiple inheritance. But you know that in Java, multiple inheritance through classes is not supported. (You need interfaces to implement the concept of multiple inheritance.)
Q&A Session
- 1.
How can you implement class adapter design patterns in Java?
You can subclass an existing class and implement the desired interface. For example, if you want to use a class adapter instead of an object adapter in the modified implementation, then you can use the following code.class TriangleClassAdapter extends Triangle implements RectInterface{public TriangleClassAdapter(double base, double height) {super(base, height);}@Overridepublic void aboutRectangle(){aboutTriangle();}@Overridepublic double calculateAreaOfRectangle(){return calculateAreaOfTriangle();}}But note that you cannot always apply this approach. For example, consider when the Triangle class is a final class (so, you cannot derive from it). Apart from this case , you will be blocked again when you notice that you need to adapt a method that is not specified in the interface. So, in cases like this, object adapters are useful.
- 2.
“Apart from this case, you will be blocked again when you notice that you need to adapt a method that is not specified in the interface.” What do you mean by this?
In the modified implementation, you have used the aboutRectangle() and aboutTriangle() methods .These methods are actually telling about the objects of the Rectangle and Triangle classes. Now, say, instead of aboutTriangle() , there is a method called aboutMe(), which is doing the same but there is no such method in the RectInterface interface. Then it will be a challenging task for you to adapt the aboutMe() method from the Triangle class and write code similar to this:for(RectInterface rectObjects:rectangleObjects){rectObjects.aboutMe();} - 3.
Which do you prefer—class adapters or object adapters?
In most cases, I prefer compositions over inheritance. Object adapters use compositions and are more flexible. Also, in many cases, you may not implement a true class adapter. (In this context, you may go through the answers to the previous questions again.)
- 4.
What are the drawbacks associated with this pattern?
I do not see any big challenges. I believe that you can make an adapter’s job simple and straightforward, but you may need to write some additional code. But the payoff is great—particularly for those legacy systems that cannot be changed but you still need to use them for their stability.
At the same time, experts suggest that you do not use different types of validations or add a new behavior to the adapter. Ideally, the job of an adaptar should be limited to only performing simple interface translations.