© Vaskaran Sarcar 2019
Vaskaran SarcarJava Design Patternshttps://doi.org/10.1007/978-1-4842-4078-6_8

8. Adapter Pattern

Vaskaran Sarcar1 
(1)
Bangalore, Karnataka, India
 

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.

Figure 8-1 shows the case before using an adapter.
../images/395506_2_En_8_Chapter/395506_2_En_8_Fig1_HTML.jpg
Figure 8-1

Before using an adapter

Figure 8-2 shows the case after using an adapter.
../images/395506_2_En_8_Chapter/395506_2_En_8_Fig2_HTML.jpg
Figure 8-2

After using an adapter

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

Figure 8-3 shows the class diagram.
../images/395506_2_En_8_Chapter/395506_2_En_8_Fig3_HTML.jpg
Figure 8-3

Class diagram

Package Explorer View

Figure 8-4 shows the high-level structure of the program.
../images/395506_2_En_8_Chapter/395506_2_En_8_Fig4_HTML.jpg
Figure 8-4

Package Explorer view

Implementation

package jdp2e.adapter.demo;
class Rectangle
{
    public double length;
    public double width;
}
class Calculator
{
    public double getArea(Rectangle rect)
    {
        return rect.length * rect.width;
    }
}
class Triangle
{
    public double base;//base
    public double height;//height
    public Triangle(int b, int h)
    {
        this.base = b;
        this.height = h;
    }
}
class CalculatorAdapter
{
    public double getArea(Triangle triangle)
    {
        Calculator c = new Calculator();
        Rectangle rect = new Rectangle();
        //Area of Triangle=0.5*base*height
        rect.length = triangle.base;
        rect.width = 0.5 * triangle.height;
        return c.getArea(rect);
    }
}
class AdapterPatternExample {
    public static void main(String[] args) {
        System.out.println("***Adapter Pattern Demo*** ");
        CalculatorAdapter calculatorAdapter = new CalculatorAdapter();
        Triangle t = new Triangle(20,10);
        System.out.println("Area of Triangle is " + calculatorAdapter.getArea(t) + " Square unit");
    }
}

Output

Here’s the output.
***Adapter Pattern Demo***
Area of Triangle is 100.0 Square unit

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

../images/395506_2_En_8_Chapter/395506_2_En_8_Figa_HTML.jpg

Key Characteristics of the Modified Implementation

The following are the 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

Figure 8-5 shows the structure of the modified program.
../images/395506_2_En_8_Chapter/395506_2_En_8_Fig5_HTML.jpg
Figure 8-5

Modified Package Explorer view

Modified Implementation

This is the modified implementation.
package jdp2e.adapter.modified.demo;
import java.util.ArrayList;
import java.util.List;
interface RectInterface
{
    void aboutRectangle();
    double calculateAreaOfRectangle();
}
class Rectangle implements RectInterface
{
    public double length;
    public double width;
    public Rectangle(double length, double width)
    {
        this.length = length;
        this.width = width;
    }
    @Override
    public void aboutRectangle()
    {
        System.out.println("Rectangle object with length: "+ this.length +" unit and width :" +this.width+ " unit.");
    }
    @Override
    public double calculateAreaOfRectangle()
    {
        return length * width;
    }
}
interface TriInterface
{
    void aboutTriangle();
    double calculateAreaOfTriangle();
}
class Triangle implements TriInterface
{
    public double base;//base
    public double height;//height
    public Triangle(double base, double height)
    {
        this.base = base;
        this.height = height;
    }
    @Override
    public void aboutTriangle() {
        System.out.println("Triangle object with base: "+ this.base +" unit and height :" +this.height+ " unit.");
    }
    @Override
    public double calculateAreaOfTriangle() {
        return 0.5 * base * height;
    }
}
/*TriangleAdapter is implementing RectInterface.
 So, it needs to implement all the methods defined
in the target interface.*/
class TriangleAdapter implements RectInterface
{
    Triangle triangle;
    public TriangleAdapter(Triangle t)
    {
        this.triangle = t;
    }
    @Override
    public void aboutRectangle() {
        triangle.aboutTriangle();
    }
    @Override
    public double calculateAreaOfRectangle() {
        return triangle.calculateAreaOfTriangle();
    }
}
class ModifiedAdapterPatternExample {
    public static void main(String[] args) {
        System.out.println("***Adapter Pattern Modified Demo*** ");
        Rectangle rectangle = new Rectangle(20, 10);
        System.out.println("Area of Rectangle is :  "+ rectangle.calculateAreaOfRectangle()+" Square unit.");
        Triangle triangle = new Triangle(10,5);
        System.out.println("Area of Triangle is : "+triangle.calculateAreaOfTriangle()+ " Square unit.");
        RectInterface adapter = new TriangleAdapter(triangle);
        //Passing a Triangle instead of a Rectangle
        System.out.println("Area of Triangle using the triangle adapter is : "+getArea(adapter)+" Square unit.");
        //Some Additional code (Optional) to show the power of adapter
        //pattern
        List<RectInterface> rectangleObjects=new ArrayList<RectInterface>();
        rectangleObjects.add(rectangle);
        //rectangleObjects.add(triangle);//Error
        rectangleObjects.add(adapter);//Ok
        System.out.println("");
        System.out.println("*****Current objects in the system are:******");
        for(RectInterface rectObjects:rectangleObjects)
        {
            rectObjects.aboutRectangle();
        }
    }
    /*getArea(RectInterface r) method  does not know that through TriangleAdapter, it is getting a Triangle  object instead of a Rectangle object*/
    static double getArea(RectInterface r)
    {
        return r.calculateAreaOfRectangle();
    }
}

Modified Output

This is the modified output.
***Adapter Pattern Modified Demo***
Area of Rectangle is :  200.0 Square unit.
Area of Triangle is : 25.0 Square unit.
Area of Triangle using the triangle adapter is : 25.0 Square unit.
*****Current objects in the system are:******
Rectangle object with length: 20.0 unit and width :10.0 unit.
Triangle object with base: 10.0 unit and height :5.0 unit.

Types of Adapters

GoF explains two types of adapters: class adapters and object adapters.

Object Adapters

Object adapters adapt through object compositions, as shown in Figure 8-6. The adapter discussed so far is an example of an object adapter.
../images/395506_2_En_8_Chapter/395506_2_En_8_Fig6_HTML.jpg
Figure 8-6

A typical object adapter

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.)

Figure 8-7 shows the typical class diagram for class adapters, which support multiple inheritance.
../images/395506_2_En_8_Chapter/395506_2_En_8_Fig7_HTML.jpg
Figure 8-7

A typical class adapter

Q&A Session

  1. 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);
        }
        @Override
        public void aboutRectangle()
        {
            aboutTriangle();
        }
        @Override
        public 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. 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. 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. 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.

     
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.34.25