Introduction
This article aims at understanding when we could find the Adapter pattern useful and how can we have a rudimentary implementation of the Adapter pattern using C# and C++.
Background
It was almost 6 years back, at the very beginning of my career, that one of my clients told me to write an adapter. Back then I was unaware of the "Adapter Pattern"
but I took his words literally and started my work. I did the required task and wrote an adapter. I think this pattern is something that anyone will find very
easy to understand and even implement.
What exactly is an adapter? If I have a source providing me something in some format and my target is expecting that stuff in some other format, then I can hook
in a module in between these two guys that will do the conversion. This entity working in between these two is my adapter. I remember the adapter that I used
to power up my 16 bit gaming console (expecting 9 volts of power) using a 220 volt power supply.
The Adapter we are going to talk about here is also on the same lines but it works for classes and objects. If I have a class that is exposing some functions but my client
is expecting some other interface, then I can have an adapter in between. The benefit of having an adapter is that the client need not be changed every time I
choose to change my underlying object that is being used. I just have to have an adapter written for the new target and the client code will work seamlessly.
Using the Code
GoF defines Adapter pattern as "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."
Let us try to understand this class diagram:
Target
: Defines the interface that the client uses.Adapter
: It uses (adapts) the Adaptee
's interface and exposes the Target
interface to the client.Adaptee
: This is the object whose interface needs to be adapted.Client
: It uses the Adaptee
functionality via the adapter interface, i.e., Target
.
One good example of when we could find this pattern useful is when visualizing an MP3 player application. This MP3 player application was developed on top of some
library (say DirectX). The application makes DirectX specific calls to achieve the playback functionality. After some time, there is a requirement of using XNA for
playback instead of DirectX. This could mean either rewriting the complete application to change the DirectX specific calls to XNA specific calls, or
we could simply write an adapter for XNA in such a way that the client application could work with minimum or no changes.
Let us try to write a small toy application to demonstrate this concept. We will create a small client that will use two libraries (dummy) to perform some operations.
These two libraries expose different interfaces so we will write a small adapter to use the libraries. Let us look at the two libraries first, Adaptee
:
class LibraryOne
{
public void ThisIsHowOneDoesIt()
{
Console.Write("Using Library ONE to perform the action\n");
}
}
class LibraryTwo
{
public string ThisIsHowTwoDoesIt()
{
return "Using Library TWO to perform the action";
}
}
Now let us look at the C++ Implementation of these Libraries i.e. Adaptee
class LibraryOne
{
public:
void ThisIsHowOneDoesIt()
{
std::cout<< "Using Library ONE to perform the action\n";
}
};
class LibraryTwo
{
public:
std::string ThisIsHowTwoDoesIt()
{
return "Using Library TWO to perform the action\n";
}
};
Now let us write the adapter interface for our concrete adapters, i.e., Target
from the above class diagram.
interface IAdapter
{
void Do();
}
The C++ Implementation of the Target
i.e. IAdapter
class IAdapter
{
public:
virtual void Do() = 0;
};
and now we will write the concrete Adapter classes to use the two libraries:
class AdapterOne : IAdapter
{
private LibraryOne one = null;
public AdapterOne()
{
one = new LibraryOne();
}
#region IAdapter Members
public void Do()
{
one.ThisIsHowOneDoesIt();
}
#endregion
}
class AdapterTwo : IAdapter
{
private LibraryTwo two = null;
public AdapterTwo()
{
two = new LibraryTwo();
}
#region IAdapter Members
public void Do()
{
Console.WriteLine(two.ThisIsHowTwoDoesIt() + "\n"); ;
}
#endregion
}
The C++ Implementation of Concrete Adapters is
class AdapterOne : public IAdapter
{
public:
void Do()
{
LibraryOne one;
one.ThisIsHowOneDoesIt();
}
};
class AdapterTwo : public IAdapter
{
public:
void Do()
{
LibraryTwo two;
std::cout << two.ThisIsHowTwoDoesIt();
}
};
So now we have a single interface that the client can use. The respective adapters will take care of making the calls to the respective underlying objects.
Let us see how the Client
can use this adapter.
static void Main(string[] args)
{
IAdapter adapter = null;
Console.WriteLine("Enter which library you wanna use to do operation {1,2}");
int x = Console.Read();
if (x == '1')
{
adapter = new AdapterOne();
}
else if (x == '2')
{
adapter = new AdapterTwo();
}
adapter.Do();
}
and finally the C++ Implementation of Client
int _tmain(int argc, _TCHAR* argv[])
{
IAdapter *adapter = 0;
cout << "Enter which library you wanna use to do operation {1,2}";
int x;
cin >> x;
if (x == 1)
{
adapter = new AdapterOne();
}
else if (x == 2)
{
adapter = new AdapterTwo();
}
adapter->Do();
delete adapter;
return 0;
};
So now the client can use the same interface to perform operations using both underlying objects. The Adapter pattern is particularly useful when we have two classes that
perform similar functions but have different interfaces. The client wants to use both classes, so instead of having conditional code scattered all around
the client code, it would be better and simpler to understand if these two classes share the same interface.
We cannot change the class interfaces, but we can have an Adapter which allows the client to use a common interface to access the underlying classes.
Before wrapping up, let us look at the class diagram of our small application and compare it with the GoF diagram.
Points of Interest
This article was written from a beginner's point of view and demonstrated the implementation of the Adapter pattern using a non-realistic example. The Adapter pattern
is a really simple but important Design Pattern. The .NET Framework uses this pattern in its core to use the legacy COM component.
History
- 06 March 2012: First version.
- 20 March 2012: Added the C++ Implementation for Adapter Pattern.