- Back to Home »
- Adapter
Posted by : Sushanth
Tuesday, 15 December 2015
Adapter:
“Convert the interface of a
class into another interface clients expect.”
Adapter lets classes work together that
couldn't otherwise because of incompatible interfaces.
Motivation:
Sometimes a toolkit or class library can
not be used because its interface is incompatible with the interface required
by an application. We can not change the library interface, since we may not
have its source code. Even if we did have the source code, we probably should
not change the library for each domain-specific application.
Adapter is the only design pattern which
is of both the scopes ie it can be of Class scope as well as of Object scope ie
Adapters in software can be implemented both using class inheritance as well as
object composition.
Class
Level Adapter:
In this class diagram , there are
two classes representing two systems viz the SrcSystem and the TargetSystem.
These are two classes have heterogeneous interfaces. Heterogenity in interfaces
is depicted here by having making the SrcEntity and TargetSystem having
different signatures. SrcEntity has a method request() while the TargetSystem
has a method specificRequest(). There is also a class - Adapter representing
the adapter. As the Adapter should know both the interfaces, one way of doing
the same is by using multiple inheritance ie Making the Adapter class inherit
from both the SrcEntity and the TargetSystem. As the Adapter is inheriting from
the SrcEntity , it can override the request() in the Adapter class. The Adapter
in turn can call the specificRequest() method of the TargetSystem.
Hence when the client invokes the
request() method the request() method of the Adpater will get called. If any preprocessing
needs to be done will be done and then the specificRequest() method of the
TargetSystem will be called. The results from the TargetSystem is post
processed and finally returned to the client. This is how adaption happens in a
class level adapter.
Object
Level Adapter:
The difference being , here
instead of having a class called as TargetSystem, we will have an interface
called as TSInterface which has a method called specificRequest(). There can be
one or more implementation classes of this interface which will override the
specificRequest() method providing different implementations. Also in the
Adapter class instead of extending from the TargetSystem as in the class level
adapter , the Adapter will now keep a reference to the TSInterface.
Hence for any request from the
client the request() method of the Adapter gets called … any translations that
need to be done can be performed in the preprocessing stage of the method,
subsequently the specificRequest() method is invoked on one of the implementation
classes. Any return results may be retranslated in the post processing stage
and the result will then be returned to the Client.
What can an Object level adapter do that a Class level adapter
cannot do?
1.) A Class level adapter can adapt a single
source to single target while an Object level adapter can adapt a single source
to multiple targets. As Object level adapter uses Object Composition which is
nothing but simple association and polymorphism , hence its gets the
flexibility of Adapting a single source to multiple targets.
2.) As there is no level of
indirection involved, Class level adapters gives better performance as compared
to Object level adapters.
Now what can a Class level adapter do that an Object level adapter
cannot do?
1.) Class level adapter if need be
can override the specificRequest() method [due to multiple inheritance ] of the
TargetSystem which an Object Level adapter cannot do. Object level adapter
cannot do this as it just keeps a reference to the TargetSystem.
2.)Another problem with class
level adapters is only C++ as a programming language supports multiple
inheritance while most of the other frequently used programming languages don’t
support multiple inheritance. Hence Class level adapters are mostly limited to
C++ programming language.
3.)As flexibility is desirable in
most of the cases, Object level adapters are more frequently used as compared
to Class level adapters.
Check list:
- Identify the players: the component(s) that want to be
accommodated (i.e. the client), and the component that needs to adapt
(i.e. the adaptee).
- Identify the interface that the client requires.
- Design a “wrapper” class that can “impedance match” the adaptee
to the client.
- The adapter/wrapper class “has a” instance of the
adaptee class.
- The adapter/wrapper class “maps” the client interface to the
adaptee interface.
- The client uses (is coupled to) the new interface
Example 1:
Consider a utility class that has a
copy() method which can make a copy of an vector excluding those objects that meet
a certain criteria. To accomplish this method assumes that all objects in the
vector implement the Copyable interface providing the isCopyable() method to
determine if the object should be copied or not.
Here’s the Copyable interface:
public interface Copyable {
public
boolean isCopyable();
}
And here’s the copy() method of the VectorUtilities class:
public static Vector copy(Vector
vin) {
Vector
vout = new Vector();
Enumeration
e = vin.elements();
while
(e.hasMoreElements()) {
Copyable
c = (Copyable) e.nextElement();
if
(c.isCopyable())
vout.addElemet(c);
}
return
vout;
}
But what if we have a class, say the
Document class that does not implement the Copyable interface. We want to be
able perform a selective copy of a vector of Document objects, but we do not want
to modify the Document class at all. To make things simple, let’s assume that
the Document class has isValid() method we can invoke to determine whether or not
it should be copied.
public
class DocumentAdapter implements Copyable {
private Document d;
public DocumentAdapter(Document d) {
document
= d;
}
public boolean isCopyable() {
return
d.isValid();
}
}
Example 2:
#include "stdafx.h"
#include <iostream>
usingnamespace
std
;
class ExecuteInterface {
public
:
// 1. Specify the new interface
virtual
~ExecuteInterface(){}
virtual
void
execute() =
0;
};
// 2. Design a "wrapper" or "adapter" class
template<
classTYPE>
classExecuteAdapter:
publicExecuteInterface {
public
:
ExecuteAdapter(TYPE *o,
void(TYPE:: *m)()) {
object = o;
method = m;
}
~ExecuteAdapter() {
delete
object;
}
// 4. The adapter/wrapper "maps" the new to the legacy implementation
void
execute() {
/* the new */
(object->*method)();
}
private
:
TYPE *object;
// ptr-to-object attribute
void
(TYPE:: *method)();
/* the old */// ptr-to-member-function attribute
};
// The old: three totally incompatible classes
// no common base class,
class Fea {
public
:
// no hope of polymorphism
~Fea() {
cout
<<
"Fea::dtor"<< endl;
}
void
doThis() {
cout
<<
"Fea::doThis()"<< endl;
}
};
class Feye {
public
:~Feye() {
cout
<<
"Feye::dtor"<< endl;
}
void
doThat() {
cout
<<
"Feye::doThat()"<< endl;
}
};
class Pheau {
public
:
~Pheau() {
cout
<<
"Pheau::dtor"<< endl;
}
void
doTheOther() {
cout
<<
"Pheau::doTheOther()"<< endl;
}
};
/* the new is returned */
ExecuteInterface **initialize() {
ExecuteInterface **array =
newExecuteInterface *[
3];
/* the old is below */
array[
0] =
newExecuteAdapter < Fea > (
newFea(), &Fea::doThis);
array[
1] =
newExecuteAdapter < Feye > (
newFeye(), &Feye::doThat);
array[
2] =
newExecuteAdapter < Pheau > (
newPheau(), &Pheau::doTheOther);
return
array;
}
int main() {
ExecuteInterface **objects = initialize();
for
(
inti =
0; i <
3; i++) {
objects[i]->execute();
}
// 3. Client uses the new (polymporphism)
for
(i =
0; i <
3; i++) {
delete
objects[i];
}
delete
objects;
return
0
;
}
Example 3:
/*
* This is
our adaptee, a third party implementation of a
* number
sorter that deals with Lists, not arrays.
*/
public
class
NumberSorter
{
public
List<Integer>
sort(List<Integer> numbers)
{
//sort and return
return
new
ArrayList<Integer>();
}
}
Our Client deals with
primitive arrays rather than Lists. For the sake of this example, lets say
we can't change the client to use Lists.
int[]
numbers = new
int[]{34, 2, 4, 12, 1};
Sorter
sorter = new
SortListAdapter();
sorter.sort(numbers);
//this is
our Target interface
public
interface
Sorter
{
public
int[]
sort(int[] numbers);
}
public
class
SortListAdapter
implements
Sorter
{
public
int[]
sort(int[] numbers)
{
//convert the array to a List
List<Integer> numberList = new
ArrayList<Integer>();
//call the adapter
NumberSorter sorter = new
NumberSorter();
numberList = sorter.sort(numberList);
//convert the list back to an array and
return
return
sortedNumbers;
}
}