Published:
31 Oct 2006
|
Abstract
In this tutorial we examine the adapter pattern which is one of the most popular patterns. It allows us to adapt an existing class to new clients in a transparent manner. Hence, legacy systems often depend on it for compatibility with new systems. In fact .NET's interoperability with VB6 is based on the principles found in this pattern. |
|
by David Simmonds
Feedback
|
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days):
53776/
84
|
|
|
Introduction |
This article examines the usage of Adapter pattern using
Visual Basic .NET 2003 with the help of a sample application.
|
Intent according to the GoF |
"Convert the interface of a class into another interface
clients expect. Adapter lets classes work together that couldn’t otherwise because
of incompatible interfaces"
|
Intent seen through Real Life |
Makeup is the classic adapter; it converts the interface of
the actor (facial-makeup for actors and body-airbrushing for models) into the
interface expected (perfect pretty people) by their client (viewers/audience).
Behind the scenes, a car’s odometer converts the number of
turns of the wheel into a representation of the number of miles the driver has traveled.
How does it do this? A cable runs from the gearbox up to the dash-board of
the car and turns a series of minute gears called cog-wheels. Naturally, since
the engine is turning the gears in the gearbox, it also turns the speedo-cable
and for every couple of hundred turns of the wheel, the mile-digit of the odometer
kicks over and shows a mile. The speedometer is a bit trickier. Based on
Faraday’s principle, electromagnets within the speedometer (again turned by the
speedo-cable) turn past a stationary magnet. The faster the cable spins, the
faster the magnets crash through the stationary magnetic field and the greater
the voltage induced in the electromagnet. Your speedometer cable is in effect
a crude electricity generator/voltmeter since the speedo-needle simply measures
the voltage induced in the magnet.
By now you are wondering whether this tutorial has gone 50
mph down the wrong road. Not quite, you see another adapter that many folks
will be familiar with is the speedometer and odometer. These devices do several
adaptations behind the scenes in a manner which makes for seamless
interpretation of speed and distance traveled respectively. But in reality,
they are not measuring speed, but the number of turns of the wheel. This is
why if you jack up the car and turn the wheel, your “speed” seems to go from 0
to 10mph or so.
This illustrates the workings of the adapter pattern quite
nicely. Supposing I said my 14 year old carbureted car (properly tuned) gives
me 31,000 wheel-turns per gallon in the city/43000 WTPG on the highway. Admit
it, you would be quite perplexed. However, if I told you that I got 24 mpg
city/33 Highway then you would respond without thinking, “That piece of junk
gives such great mileage?" I would be offended, but complimented simultaneously
since I love my old car and feel quite proud that I can keep it running well. You
see, the great job of adaptation which the odometer does allows you the
listener to be able to relate to fuel efficiency when it is measured in miles
per gallon. You think in miles, you calculate your trip in miles; you
calculate your trip costs in miles. And so the odometer adapts the
wheel-revolutions to a figure you can relate to and work with in a meaningful
way.
In the adapter pattern, there is an adaptee class. This
class has an interface which clients cannot be expected to work with for one
reason or the other. In most descriptions of the pattern its incompatibility
is because the client has already been wired-up to interact with some other
interface. In our scenario, the adaptee is the wheel. The interface which the
wheel exposes is the wheel-turn-rate. It is the job of the cable/cog-wheels to
capture the wheel turn rate and turn it into something readable by humans. Similarly,
in the adapter pattern the adapter inherits the interface which is un-readable
and presents an interface which is readable. In the speedometer example we
have one final item and this is the speedometer/odometer display. This is the
public and visible interface which the driver will glance at frantically when
he sees the policeman pointing something at him. He will brake until the
speedometer shows him going a speed within the legal limit. Similarly, in the
adapter pattern the Target is the interface which the client will be able to
interact with and do useful work through.
The client in the adapter pattern interacts with the target
interface. This is mirrored in the motoring situation where the driver (the
client) reads the speedometer (target interface).
Putting it all together, we have an adaptee class which
clients do not know how to work with, an adapter class which is configured with
the interface of the adaptee and so uses the adaptee’s outputs as its inputs. Finally,
it has a public interface which is useful to the client and so the client
interacts with the adapter through public interface it exposes and gets the
adapted outputs.
In fact any device or system which performs conversion shows
the intent and operation of the adapter pattern. The adapter pattern could have
easily been called the converter pattern.
There is a trick in all of this speed business. Did you
know that changing the size of your tires will change the accuracy of your
speedometer and odometer? This is due to the fact that 50mph always translates
to a fixed wheel turn rate. This is unchangeable. However, with bigger tires,
every turn of your wheel means you are actually covering more distance. So
your odometer, trip-meter, speed and mpg calculations all get thrown off with a
changing tire size. Technically, the fact that your tires experience
tread-wear means that there is also minute and very gradual variation in all
the above measures as time goes by. This brings up an important point. In
some cases, adapters may need to be calibrated (see the pattern-tweaking
section).
In the GoF world, we actually have two major types of
adapters. The class adapter inherits publicly from the target class and
privately from the adaptee class. In C++ you can do multiple inheritances, but
not so in VB.NET. So in VB.NET we publicly implement the interface of the
adapter and privately inherit the target class.
In the case of object adapters, we inherit publicly from the
target interface while containing an object reference to the adaptee.
|
Intent seen through O/S & Applications |
Ever used Virtual PC on a Macintosh? This is a program
which allows you to install Windows on a Macintosh and pretend that you are
using a Wintel setup. I understand that you can actually install Windows XP
directly on Mac OS these days, but a couple years ago Virtual PC was one of the
ways you did it.
Anyway, the point is that it allows you to adapt the
adaptee’s interface (Mac Hardware’s interface) to the adaptee (the Wintel
interface). You can install Windows XP programs as if you are working with a
Wintel, so you are actually exposing the Wintel interface publicly (the target).
|
Scenario - Sample Code |
I know I promised to give sample code which was different
from the Real Life illustration, but I like this example so much that I am
going to continue using it.
In the example we have a Windows Form representing the
dashboard, gas pedal and wheel-turns of a car. We start with the accelerator
pedal which “drives” everything. Sliding the horizontal scroll bar to the
right simulates the pressing of the accelerator. Behind the scenes and
somewhere in imagination-land, there is a consequent increased revving of the
engine, which turns the gears and makes the axle turn faster. Since the wheels
are connected to the axle, this should translate into increased ground speed.
The speedometer cable is connected to the axle and therefore
spins as fast as the axle. To get the speed we simply translate the revolutions
of the speedo-cable to miles and its turn-rate to mph.
UML – General
Figure 1
UML – Sample Code
Figure 2
|
Participants – Sample Code |
Target–Miles driven
The interface which is useful to the client (driver). In
this case it is a non-existent class whose main member variable is
miles-driven.
Adaptee – Axle
Exposes an interface which is not usable by the client
(m_WheelTurnRate) and so needs adaptation.
Adapter – Odometer/Speedometer
Converts the interface exposed by the axle into the
interface expected by the driver. In the case of the Speedometer, it converts
wheel-turn rate to ground speed in mph. The odometer in the sample code is an
object adapter which accesses the state within the Axle object which it
references. The odometer is a class adapter which inherits from the Axle class
and converts the number of wheel turns into distance traveled.
Client – DoDashboardUpdates
Utilizes the converted method or state which the adapter
makes available.
Axle (object adapter example)
As discussed earlier, the axle is doing its thing, spinning
away and turning the wheels. So what we get from the axle is just the
wheel-turn rate and number of wheel-turns. It has nothing to do with the
pattern in the sense that it has no knowledge of the other participants in the
pattern, even though the other participants know about it. This is typical of
adaptees.
Listing 1
Public Class Axle ' Turns the wheels
Private Shared CarAxle As Axle
Private m_WheelTurnRate As Integer
Public Shared Function GetAxle() As Axle
If CarAxle Is Nothing Then
CarAxle = New Axle
End If
Return CarAxle
End Function
Public Property WheelTurnRate() As Integer
Get
Return m_WheelTurnRate
End Get
Set(ByVal Value As Integer)
m_WheelTurnRate = Value
End Set
End Property
End Class
Sub-Axle (class adapter example)
This is just another incarnation of the axle which we
include for convenience in order to demonstrate the class-adapter setup which
is utilized by the odometer.
Listing 2
Public Class SubAxle 'Turns the wheels
Friend Sub New()
End Sub
Private m_WheelTurnRate As Integer
Public Property WheelTurnRate() As Integer
Get
Return m_WheelTurnRate
End Get
Set(ByVal Value As Integer)
m_WheelTurnRate = Value
End Set
End Property
End Class
|
Odometer and Speedometer |
These are the two object adapter classes. They use object
composition/class inheritance by holding a reference to the axle object (the
adaptee). They use their respective adaptation-functions to convert the
not-so-useful wheel-turn rate and wheel-turns into ground-speed and
miles-driven respectively. The functions which achieve these adaptations are
UpdateMileage and MeasureSpeed respectively.
Listing 3
Public Class Odometer
Private m_OdometerCable As Axle
Private m_MilesDriven As Double
Private PreviousSampleTime As Date
Public ReadOnly Property MilesDriven()
End Property
Public Function UpdateMileage(ByVal WheelTurnsPerMile As Single)
m_MilesDriven + = m_OdometerCable.WheelTurnRate / WheelTurnsPerMile / _
3600 * DateDiff(DateInterval.Second, PreviousSampleTime, Now())
PreviousSampleTime = Now()
End Function
End Class
Public Class Speedometer
Private Shared CarSpeedometer As Speedometer
Public m_SpeedometerCable As Axle
Private m_GroundSpeed As Integer
Private Sub New(ByVal AxleConnection)
m_SpeedometerCable = AxleConnection
End Sub
Public Function MeasureSpeed(ByVal WheelTurnsPerMile As Single) As Integer
m_GroundSpeed = m_SpeedometerCable.WheelTurnRate / WheelTurnsPerMile
Return m_GroundSpeed
End Function
End Class
MileMeter
This is the class adapter version. It inherits from the
axle class (the adaptee) while implementing Interface MileMeter. In order to
utilize it, we will instantiate it and call the Method which was implemented. Internally,
this method accesses the inherited method/property from the inherited class:
Me.WheelTurnRate. Notice that because WheelTurnRate is inherited, it is
internal to the class-adapter and so we do not make a reference looking like:
m_OdometerCable.WheelTurnRate.
Listing 4
Public Interface MileMeter
Function MeasureMilesDriven(ByVal WheelTurnsPerMile As Single) As Single
End Interface
Public Class CableReader ' Class adapter
Inherits SubAxle ' Inherit adaptee
Implements MileMeter ' Implement target interface
Dim PreviousSampleTime As DateTime
Public Sub New()
MyBase.New()
PreviousSampleTime = Now
End Sub
Public Function MeasureMilesDriven( _
ByVal WheelTurnsPerMile As Single) As Single Implements MileMeter.MeasureMilesDriven
Dim MileageChange As Single
MileageChange = Me.WheelTurnRate / WheelTurnsPerMile / _
3600 * DateDiff(DateInterval.Second, PreviousSampleTime, Now())
PreviousSampleTime = Now
Return MileageChange
End Function
End Class
Client
This is the class adapter version. It inherits from the
axle class (the adaptee) while implementing Interface MileMeter. In order to
utilize it, we will instantiate it and call the Method which was implemented. Internally,
this method accesses the inherited method/property from the inherited class:
Me.WheelTurnRate. Notice that because WheelTurnRate is inherited, it is
internal to the class-adapter and so we do not make a reference looking like:
m_OdometerCable.WheelTurnRate.
Listing 5
Private Sub Form1_Load(_
ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
MyAxle = Axle.GetAxle
WheelOnCar = New Wheel
WheelTurnsPerMile = 1600 / (WheelOnCar.OverallDiameter * Math.PI)
MyOdometer = Odometer.GetOdometer(MyAxle, LastMileageRecorded)
MySpeedometer = Speedometer.GetSpeedometer(MyAxle)
TripMeter = New CableReader
End Sub
Public Sub RenderSpeed(ByVal SpeedToDisplay As Integer)
Dim LocationOfSpeed As New Point
Dim AngleInRadians As Single
Const NeedleLength As Integer = 70
Const XOffset As Integer = 110 ' X coordinate of the point about which the needle rotates
Const YOffset As Integer = 100 ' Y coordinate of the point about which the needle rotates
Dim RotationPoint As New Point(XOffset, YOffset)
AngleInRadians = 3 * SpeedToDisplay * Math.PI / 180
LocationOfSpeed.X = -1 * (Math.Cos(AngleInRadians) * NeedleLength) + XOffset
LocationOfSpeed.Y = -1 * (Math.Sin(AngleInRadians) * NeedleLength) + YOffset
NeedleGraphics.Clear(Color.FromKnownColor(KnownColor.Control))
Dim NeedlePen As New Pen(Color.Red, 3)
NeedleGraphics.DrawLine(NeedlePen, RotationPoint, LocationOfSpeed)
End Sub
Private Sub vsbGasPedal_Scroll( … ) Handles vsbGasPedal.Scroll
MyAxle.WheelTurnRate = CInt(vsbGasPedal.Value) ' Object Adapter
If MyAxle.WheelTurnRate > 55000 Then
MyAxle.WheelTurnRate = 55000 ' Speed limited :-)
End If
TripMeter.WheelTurnRate = MyAxle.WheelTurnRate ' Class adapter
lblWheelTurnRate.Text = CInt(Me.MyAxle.WheelTurnRate / 3600)
DoDashboardUpdates()
End Sub
Private Sub Timer1_Tick( … ) Handles Timer1.Tick
DoDashboardUpdates()
End Sub
Public Sub DoDashboardUpdates()
RenderSpeed(MySpeedometer.MeasureSpeed(WheelTurnsPerMile))
MyOdometer.UpdateMileage(WheelTurnsPerMile)
lblMilesDriven.Text = Math.Floor(MyOdometer.MilesDriven)
TenthValueToDisplay = MyOdometer.MilesDriven * 10 Mod 10
If TenthValueToDisplay = 10 Then
TenthValueToDisplay = 0
End If
lblMilesDriven_Tenth.Text = TenthValueToDisplay
MilesInthisTrip + = TripMeter.MeasureMilesDriven(WheelTurnsPerMile)
lblMilesDrivenInTrip.Text = Math.Floor(MilesInthisTrip)
TenthValueToDisplay = MilesInthisTrip * 10 Mod 10
If TenthValueToDisplay = 10 Then
TenthValueToDisplay = 0
End If
lblTripMiles_Tenths.Text = (MilesInthisTrip * 10) Mod 10
End Sub
End Class
|
Interface issues |
This pattern is all about interfaces. It is, therefore,
hard to summarize the interface issues in two paragraphs. However, the key
point to understand is that the adapter implements the target interface and so
exposes methods which are useful to the client. This is an interacting
interface which the clients will access since they should be able to switch seamlessly
between interacting with a class, which naturally exposes the target interface
and an adaptee which exposes the target interface through the adapter.
|
Namespace/Scope/Accessor issues |
The adaptee should have a private relationship to the
adapter, whether it is inherited or contained. This is because we do not want
a client reaching into the internals of the adapter and getting access to the
raw, unconverted, untranslated, unadapted state. Remember that before
adaptation, the adaptee’s state is not fit for consumption. Naturally, since
the target’s interface is the interface which is acceptable to the client, then
it is the one that is exposed and public.
This is similar to the behavior of a major corporation or
political party’s P.R. department or spin-machine. Things happen day by day in
major organizations and it is preferable that the spin-doctors have a go at the
material before releasing it to the press or general public. The “unadorned
truth” reflects the adaptee’s interface while the “palatable statements,” which
can be released to the press, fits the target interface. When everything is
scoped up properly, the client becomes like the public which reads the
press-release in the paper. When we get the scoping wrong, however, the client
becomes like the investigative reporter who has inside contacts and is able to
dig mercilessly into the guts of the story and expose all the unpleasant stuff.
|
Common mistakes in Pattern Literature / Pitfalls to avoid |
There is a generally accepted feeling that Adapter is a
Post-development strategy for working with a class which was inadequately
designed in the past, in order to make it fit the current requirements. In
other words, it helps us to make the best of a bad situation. I do not agree
totally. Adapter can be designed up front to ensure that you are eventually
compatible with the class you really want to work with. The other issue is
that you are in a sense providing some of the work which the expensive toolkit
already implements. But if it provides a level of abstraction that a team of
programmers find useful in not only making them more productive, but also
providing a standard way of communicating across the project, not to mention
removing the necessity of universal recognition-zone-based coding from all
their various client codes, then it is well worth it.
|
Advanced Issues |
Useful Variations
1a Symmetrical adapters
None of the classes that the client is interested in looks
like the target. They all need to be adapted to look like what we need them to
look like. The speedometer situation is like that since all existing cars
(which I know of) translate wheel-turn rates into ground-speed. However, at
some point, the inevitable interventions of big-brother will manifest itself in
the use of GPS for speed-measurement (and of course speed-tracking and
automatic ticketing). In this case we will not need the use of an adapter
since GPS measures actual distance traveled and not “wheel-turn-rates.”
1b Asymmetrical adapters
Some adaptees have the desired interface and so do not need adaptation.
Some adaptees however, need adaptation in order to integrate with the main
system, whether from legacy systems or systems newly acquired.
2a Distributed adapter/Wide adapter
Adapts several methods within the adaptee into several
distinct methods which the target interface will utilize.
2b Solitary adapters
Adapts 1 method in the adaptee to 1 required method in the
target.
3a Extrinsic/Forward adapter
Sucks data out of the adaptee for use in the target.
3b Intrinsic/Reverse adapter
Passes data from the target to the adaptee in order to
update itself.
4ai Conversion adapter
Converts a single result or value from the adaptee into the
equivalent value for use by the target. Similar to Imperial-measurement to
Metric-measurement converters or foreign currency converters or wheel-revolutions
to distance covered converters (odometer).
4aii Translating adapter
These adapters perform language and jargon translations,
measurement-converters and also currency-rate converters typify the behavior of
these adapters.
4b Amalgamated adapters
Amalgamates several parameters coming out of the adaptee to
a single unified value going to the client (such as speedometer converting
wheel-turn-numbers and rates into a single speed).
5a Pre-adaptation
Adapts the adaptee to the target preemptively. Most pattern-authors
do not recognize this as a legitimate form of adapter. They argue that adapter
allows variation in classes after-the-fact in contrast to bridge’s accomodation
of variation before-the-fact.
5b Post-adaptation
The ideal target already exists and the adapter converts
subsequent interfaces of adaptees which do not meet the target-interface into
that interface. This is the widely recognized variation.
If you look closely you will realize that pre/post
adaptation has bearing on the concept of symmetrical/asymmetrical adaptation. Arguably,
if we know before hand how the interface needs to look but do not have the
technology to create that interface, then we will symmetrically do
pre-adaptation of all existing systems to the interface which we will target in
the future. That is just one example of a combination of the concepts.
|
Pattern Tweaking |
For some adapters which I refer to as conversion-adapters,
we could abstract out the conversion rate into a class of its own so that it
becomes an inner-class which we can then maintain more easily and very
importantly, more purposely. By encapsulating this variance, we allow for
easier maintenance. In a code-based illustration of the odometer example, we
abstract out the wheel into its own class and also abstract out the
wheel-dimensions in a manner which allows us to update it. This means that
when we fit a new tire, the car’s computer will allow us to enter a new tire
size. It would automatically recalibrate the speedometer based on the tire
size we fit. This would ensure that the number of revolutions of the wheel is
correctly converted to miles-driven/miles-per-hour. We could think of this as
a useful variation which we call the Calibrated-Adapter. By making the Adapter
accessible from outside, we improve maintainability.
|
Explanation of Pattern subscripts |
R By adapting the unusable interface of the adaptee,
we ensure reusability of previously unusable legacy code.
S+ By giving clients a modified external interface to
sensitive functionality we can hide certain capabilities that we do not want
them to be aware of; the first step to having a secret stolen is knowing that
there is one to steal.
|
Downloads |
|
Summary |
In this article you have learned about the usage of Adapter
pattern with a help of a sample application. The application spins the wheel
and the article examines the real background behind its real working.
|
|
|
User Comments
Title:
its great explaination in detail about Adapter Pattern
Name:
Elan Emerging Technologies Pvt. Ltd
Date:
2010-01-18 7:05:35 AM
Comment:
its great explaination in detail about Adapter Pattern
Thanks, Elan Emerging Technologies (EETPL) http://www.elantechnologies.com
|
Title:
Thanks
Name:
Rakesh
Date:
2008-12-08 2:59:28 PM
Comment:
Looking forward to read this article
|
Title:
Very Understandable
Name:
Srilakshmi15
Date:
2008-05-11 7:19:36 AM
Comment:
Hi Simmonds It's really good to understand without getting any confusion. Thanks a lot.
|
Title:
Not able to Download the Codes
Name:
Ritesh
Date:
2007-08-07 4:19:25 AM
Comment:
I am not able to download the codes of the above sample
|
Title:
Great! Resouces.
Name:
.Net Developer
Date:
2007-05-14 1:56:04 AM
Comment:
It's Great Resouces for abt adapter pattern. I hope you will writing usefull .net articles in future.
Jim http://www.tatvasoft.com
|
Title:
its great explaination in detail abt adapter pattern
Name:
vamshi
Date:
2006-11-14 5:27:20 AM
Comment:
its great explaination in detail abt adapter pattern with a clear explanation -not found any where else. hope i will get other pattern too
|
Title:
Adapter Explanation with real Time
Name:
Ranjith M
Date:
2006-11-03 4:03:56 AM
Comment:
Its realy cool.I went through lot of links i dont understand the pattern.The real time explanation is realy good one.Thanks a Lot.:)
|
|
Product Spotlight
|
|