© Vaskaran Sarcar 2020
V. SarcarDesign Patterns in C#https://doi.org/10.1007/978-1-4842-6062-3_8

8. Adapter Pattern

Vaskaran Sarcar1 
(1)
Garia, Kolkata, West Bengal, India
 

This chapter covers the Adapter pattern.

GoF Definition

Convert the interface of a class into another interface client’s expect. Adapter lets classes work together that otherwise could not because of incompatible interfaces.

Concept

From the GoF definition, you can guess that this pattern deals with at least two incompatible inheritance hierarchies. In a domain-specific system, the clients are habituated on how to invoke methods in software. Those methods can follow an inheritance hierarchy. Now assume that you need to upgrade your system and need to implement a new inheritance hierarchy. When you do that, you do not want to force your clients to learn the new way to access the software. So, what can you do? The solution is simple: you write an adapter that accepts client requests and translates these requests in a form that the methods in the new hierarchy can understand. As a result, clients can enjoy the updated software without any hassle.

The following examples can also help you better understand the patterns.

Real-World Example

A common use of this pattern is when you use an electrical outlet adapter/AC power adapter on international travels. These adapters can act as middlemen so that an electronic device, such as 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 electrical outlet is not compatible with your charger. In this case, you may need to use an adapter. Even a translator who is converting one language to another follows this pattern in real life.

Let’s consider a situation where you have two different shapes (e.g., Shape1 and Shape2), neither of which is a rectangle, and they look like Figure 8-1.
../images/463942_2_En_8_Chapter/463942_2_En_8_Fig1_HTML.jpg
Figure 8-1

Before using an adapter

Let’s further assume that combining these two different shapes, you need to form a rectangle. How do you proceed? One simple solution is to bring another bounded X–shaped figure (filled with color), as shown in Figure 8-2.
../images/463942_2_En_8_Chapter/463942_2_En_8_Fig2_HTML.jpg
Figure 8-2

An adapter

Then attach the three shapes, as shown in Figure 8-3.
../images/463942_2_En_8_Chapter/463942_2_En_8_Fig3_HTML.jpg
Figure 8-3

After using an adapter

In programming, you can think of Shape1 and Shape2 as two different interfaces that can’t work together unless you combine them to form a rectangle using this X-shaped figure. The X-shaped figure is playing the role of an adapter in this scenario.

Computer-World Example

Suppose that you have an application that can be broadly classified into two parts: the user interface (UI or the front end) and the database (the back end). Through the user interface, clients can pass some specific type of data or objects. Your database is compatible with those objects and can store them smoothly. Over time, you may realize that you need to upgrade your software to make your clients happy. So, you may want to allow some other type of object also to pass through the UI. But in this case, the first issue 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 these new objects to a compatible form that your existing database can accept and store.

Implementation

In the upcoming example, there are two hierarchies: one for Rectangle and one for Triangle. IRectangle interface has two methods called CalculateArea() and AboutMe() . The Rectangle class implements the IRectangle interface and form the first hierarchy as follows.
class Rectangle : IRectangle
    {
        double length;
        public double width;
        public Rectangle(double length, double width)
        {
            this.length = length;
            this.width = width;
        }
        public double CalculateArea()
        {
            return length * width;
        }
        public void AboutMe()
        {
            Console.WriteLine("Actually, I am a Rectangle");
        }
    }
The ITriangle interface has two methods: CalculateAreaOfTriangle() and AboutTriangle() . The Triangle class implements the ITriangle interface and forms another hierarchy, as follows.
class Triangle : ITriangle
    {
        double baseLength; // base
        double height; // height
        public Triangle(double length, double height)
        {
            this.baseLength = length;
            this.height = height;
        }
        public double CalculateAreaOfTriangle()
        {
            return 0.5 * baseLength * height;
        }
        public void AboutTriangle()
        {
            Console.WriteLine("Actually, I am a Triangle.");
        }
    }

These two hierarchies are easy to understand. Now, let’s look at a problem in which you need to calculate the area of a triangle using the Rectangle hierarchy .

How do you proceed? You can use an adapter to solve this problem, as shown in the following example.
/*
 * RectangleAdapter is implementing IRectangle.
 * So, it needs to implement all the methods
 * defined in the target interface.
 */
class RectangleAdapter : IRectangle
{
        ITriangle triangle;
        public RectangleAdapter(ITriangle triangle)
        {
                this.triangle = triangle;
        }
        public void AboutMe()
        {
                triangle.AboutTriangle();
        }
        public double CalculateArea()
        {
                return triangle.CalculateAreaOfTriangle();
        }
}

Notice the beauty of using the adapter. You are not making any changes to any hierarchy, and at a high level, it appears that by using the IRectangle methods , you can calculate the area of a triangle. This is because you are using the AboutMe() and CalculateArea() methods of the IRectangle interface at a high level, but inside those methods, you are invoking the ITriangle methods.

Apart from this advantage, you can also extend the benefit of using an adapter. For example, suppose that you need to have a large number of rectangles in an application, but there is a constraint on the number of rectangles you create. (For simplicity, let's assume that in an application, you are allowed to create a maximum of five rectangles and ten triangles, but when the application runs, in certain scenarios, you may need to supply ten rectangles.) In those cases, using this pattern, you can use some of the triangle objects that can behave like rectangle objects. How? Well, when using the adapter, you are calling CalculateArea(), but it is invoking CalculateAreaOfTriangle() . So, you can modify the method body as you need. For example, in your application, let's assume that each rectangle object has a length of 20 units and a width of 10 units, whereas each triangle object has a base of 20 units and a height of 10 units. So, each rectangle object has an area of 20*10=200 square units, and each triangle object has an area of 0.5*20*10=100 square units. So, you can simply multiply each triangle area by 2 to get an equivalent rectangle area and substitute (or use) it where a rectangle area is needed. I hope that this makes sense to you.

Finally, you need to keep in mind that this technique suits best when you deal with objects that are not exactly the same but very similar.

Note

In the context of the previous point, you should not try to convert a circle area to a rectangle area (or do a similar type of conversion), because they are different shapes. In this example, I talk about triangles and rectangles because they have similarities.

Class Diagram

Figure 8-4 shows a class diagram of the important parts of the program.
../images/463942_2_En_8_Chapter/463942_2_En_8_Fig4_HTML.jpg
Figure 8-4

Class diagram. Client class is not shown here.

Solution Explorer View

Figure 8-5 shows a high-level structure of the program.
../images/463942_2_En_8_Chapter/463942_2_En_8_Fig5_HTML.jpg
Figure 8-5

Solution Explorer view

Demonstration 1

Here’s the implementation.
using System;
namespace AdapterPatternDemonstration
{
    interface IRectangle
    {
        void AboutMe();
        double CalculateArea();
    }
    class Rectangle : IRectangle
    {
        double length;
        public double width;
        public Rectangle(double length, double width)
        {
            this.length = length;
            this.width = width;
        }
        public double CalculateArea()
        {
            return length * width;
        }
        public void AboutMe()
        {
            Console.WriteLine("Actually, I am a Rectangle");
        }
    }
    interface ITriangle
    {
        void AboutTriangle();
        double CalculateAreaOfTriangle();
    }
    class Triangle : ITriangle
    {
        double baseLength; // base
        double height; // height
        public Triangle(double length, double height)
        {
            this.baseLength = length;
            this.height = height;
        }
        public double CalculateAreaOfTriangle()
        {
            return 0.5 * baseLength * height;
        }
        public void AboutTriangle()
        {
            Console.WriteLine("Actually, I am a Triangle.");
        }
    }
    /*
     * RectangleAdapter is implementing IRectangle.
     * So, it needs to implement all the methods
     * defined in the target interface.
     */
    class RectangleAdapter : IRectangle
    {
        ITriangle triangle;
        public RectangleAdapter(ITriangle triangle)
        {
            this.triangle = triangle;
        }
        public void AboutMe()
        {
            triangle.AboutTriangle();
        }
        public double CalculateArea()
        {
            return triangle.CalculateAreaOfTriangle();
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("***Adapter Pattern  Demo*** ");
            IRectangle rectangle = new Rectangle(20, 10);
            Console.WriteLine("For initial verification purposes, printing the areas of both shapes.");
            Console.WriteLine("Rectangle area is:{0} Square unit", rectangle.CalculateArea());
            ITriangle triangle = new Triangle(20, 10);
            Console.WriteLine("Triangle area is:{0} Square unit", triangle.CalculateAreaOfTriangle());
            Console.WriteLine(" Now using the adapter.");
            IRectangle adapter = new RectangleAdapter(triangle);
            Console.Write("True fact : ");
            adapter.AboutMe();
            Console.WriteLine($" and my area is : {adapter.CalculateArea()} square unit.");
            // Alternative way:
            Console.WriteLine(" Using the adapter in a different way now.");
            // Passing a Triangle instead of a Rectangle
            Console.WriteLine($"Area of the triangle using the adapter is :{GetDetails(adapter)} square unit.");
            Console.ReadKey();
        }
        /*
         * The following method does not know
         * that through the adapter, it can
         * actually process a
         * Triangle instead of a Rectangle.
         */
        static double GetDetails(IRectangle rectangle)
        {
            rectangle.AboutMe();
            return rectangle.CalculateArea();
        }
    }
}

Output

Here’s the output.
***Adapter Pattern  Demo***
For initial verification purposes, printing the areas of both shapes.
Rectangle area is:200 Square unit
Triangle area is:100 Square unit
Now using the adapter.
True fact : Actually, I am a Triangle.
 and my area is : 100 square unit.
Using the adapter in a different way now.
Actually, I am a Triangle.
Area of the triangle using the adapter is :100 square unit.

Analysis

Note the following code segment with comments inside the Main() method , as follows.
/*
 * The following method does not know
 * that through the adapter, it can
 * actually process a
 * Triangle instead of a Rectangle.
 */
static double GetDetails(IRectangle rectangle)
{
        rectangle.AboutMe();
        return rectangle.CalculateArea();
}

This segment is optional. I kept it to show you where you can invoke both adaptee methods in one call.

Types of Adapters

The GoF described two types of adapters: class adapters and object adapters.

Object Adapters

Object adapters adapt through object composition, as shown in Figure 8-6. So, the adapter discussed so far is an example of an object adapter.
../images/463942_2_En_8_Chapter/463942_2_En_8_Fig6_HTML.jpg
Figure 8-6

Object adapter

In our example, RectangleAdapter is the adapter that implements IRectangle (Target interface). ITriangle is the Adaptee interface. The adapter holds the adaptee instance.

Class Adapters

Class adapters adapt through subclassing and support multiple inheritance. But you know that in C#, 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/463942_2_En_8_Chapter/463942_2_En_8_Fig7_HTML.jpg
Figure 8-7

Class adapter

Q&A Session

8.1 How do you implement a class adapter design pattern in C#?

You can subclass an existing class and implement the desired interface. Demonstration 2 shows you a complete example with output.

Demonstration 2

This demonstration shows a class adapter. To make the example short and simple, I made the IRectangle and ITriangle interfaces with only one method. IRectangle has only the AboutMe() method , and the Rectangle class implements the IRectangle interface , and thus the following hierarchy is formed.
interface IRectangle
    {
        void AboutMe();
    }
    class Rectangle : IRectangle
    {
        public void AboutMe()
        {
            Console.WriteLine("Actually, I am a Rectangle");
        }
    }
ITriangle has the AboutTriangle() method . The Triangle class implements this interface, and the following hierarchy is formed.
interface ITriangle
    {
        void AboutTriangle();
    }
    class Triangle : ITriangle
    {
        public void AboutTriangle()
        {
            Console.WriteLine("Actually, I am a Triangle");
        }
    }
Now comes our class adapter, which uses the concept of multiple inheritance using a concrete class and an interface. The attached comments help you better understand the code.
    /*
     * RectangleAdapter is implementing IRectangle.
     * So, it needs to implement all the methods
     * defined in the target interface.
     */
    class RectangleAdapter : Triangle, IRectangle
    {
        public void AboutMe()
        {
            // Invoking the adaptee method
            AboutTriangle();
        }
    }
Now you can go through the complete demonstration, which is as follows.
using System;
namespace AdapterPatternAlternativeImplementationDemo
{
    interface IRectangle
    {
        void AboutMe();
    }
    class Rectangle : IRectangle
    {
        public void AboutMe()
        {
            Console.WriteLine("Actually, I am a Rectangle");
        }
    }
    interface ITriangle
    {
        void AboutTriangle();
    }
    class Triangle : ITriangle
    {
        public void AboutTriangle()
        {
            Console.WriteLine("Actually, I am a Triangle");
        }
    }
    /*
     * RectangleAdapter is implementing IRectangle.
     * So, it needs to implement all the methods
     * defined in the target interface.
     */
    class RectangleAdapter : Triangle, IRectangle
    {
        public void AboutMe()
        {
            // Invoking the adaptee method
            AboutTriangle();
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("***Adapter Pattern Alternative Implementation Technique Demo.*** ");
            IRectangle rectangle = new Rectangle();
            Console.WriteLine("For initial verification purposes, printing the details from of both shapes.");
            Console.WriteLine("The rectangle.AboutMe() says:");
            rectangle.AboutMe();
            ITriangle triangle = new Triangle();
            Console.WriteLine("The triangle.AboutTriangle() says:");
            triangle.AboutTriangle();
            Console.WriteLine(" Now using the adapter.");
            IRectangle adapter = new RectangleAdapter();
            Console.Write("True fact : ");
            adapter.AboutMe();
        }
    }
}

Output

Here is the output.
***Adapter Pattern Alternative Implementation Technique Demo.***
For initial verification purposes, printing the details from of both shapes.
The rectangle.AboutTriangle() says:
Actually, I am a Rectangle.
The triangle.AboutTriangle() says:
Actually, I am a Triangle.
Now using the adapter.
True fact : Actually, I am a Triangle.

Analysis

This approach may not be suitable in all scenarios. For example, you may need to adapt a method that is not specified in a C# interface. In those cases, object adapters are better.

Q&A Session

8.2 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. In many cases, it is challenging to implement a true class adapter when you need to adapt a specific method from adaptee interface, but there is no close match for that in the target interface. Apart from this, if the adaptee class (Triangle in our example) is sealed, then you cannot inherit from it.

8.3 You said, “…it is challenging to implement a true class adapter when you need to adapt a specific method from an adaptee interface, but there is no close match for that in the target interface.” Can you please elaborate?

In my examples, the target interface methods and adaptee interface methods were similar. For example, in IRectangle, there is AboutMe() method , and in ITriangle, there is the AboutTriangle() method. What do they do? They state whether it is a rectangle or a triangle.

Now suppose that there is no such method called AboutMe() in IRectangle, but AboutTriangle() still exists in ITriangle. So, in a case like this, if you need to adapt the AboutTriangle() method, you need to analyze how to proceed. In our example, AboutTriangle() is a simple method, but in real-world programming, the method is much more complex, and there can be dependency associated with it. So, when you do not have a corresponding target method, you may find challenges to adapt the method from an adaptee.

8.4 I understand that clients should not know that they are using adapters. Is this correct?

Correct. I made this implementation to show you that clients do not need to know that their requests are translated through an adapter to the adaptee. If you want them to show any message, you could simply add a console message in your adapter in demonstration 2, as shown next.
class RectangleAdapter : Triangle, IRectangle
{
        public void AboutMe()
        {
                // Invoking the adaptee method
                // For Q&A
                Console.WriteLine("You are using an adapter now.");
                AboutTriangle();
        }
}

8.5 What happens if the target interface and adaptee interface method signature differ?

Not a problem at all. If an adapter method has a few parameters, you can invoke the adaptee method with some additional dummy parameters. In the Builder pattern (demonstration 2 in Chapter 3), you saw optional parameters. You can use the same concept here.

In the reverse scenario (if the adapter method has more parameters than the adaptee method), by using those additional parameters, you can add functionality before you transfer the call to the adaptee method.

Lastly, if the method parameters are incompatible, you may need to do casting (if possible).

8.6 What are the drawbacks associated with this pattern?

I do not see any major challenges. I believe that an adapter’s job is simple and straightforward, but you need to write some additional code. However, the payoff is great, particularly for those legacy systems that cannot be changed, but you still want to use them for their stability and simplicity.

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

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