Published:
04 Dec 2006
|
Abstract
The Bridge pattern is something we see everyday in corporations where management figures out what should be done by creating a strategic, abstract plan of action, while employess implement the decisions taken. In this tutorial David examines the application of Bridge Pattern with a sample Visual Basic .NET application. |
|
by David Simmonds
Feedback
|
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days):
47047/
113
|
|
|
Introduction |
The bridge pattern allows us to operate a two part system
which translates an expression of the intent to do work into the actual work to
be performed. It accomplishes this by pairing two different parts of a system
that are dependent on each other for accomplishing a given task.
When discussing the bridge pattern, though, we must be
careful from the outset to distinguish between two part systems. We are not
interested in systems where half of the work is done by one member of the team
and then the other team member takes over half way and finishes the task. For
example, we are not talking about a leer-jet/limousine teams or
commercial-airliner/taxi combinations. In these examples a tangible piece of work
is done by the first team member and this is followed by tangible work done by
the second team member.
The Bridge pattern, instead, looks at situations where the
two team members work hand-in-hand in order to accomplish the task. So we are
interested here in things like printer/cartridge, sim-card/Cell-phone,
Limousine/Chauffeur, Mother/Father, Rim/Tire combinations. Nothing at all can
be accomplished without the member on the left-side of the slash. Neither can
the member on the right-side do anything by itself. Without one or the other
you cannot begin to do anything at all. None can start the job for the other
to finish; they are both powerless without each other.
|
What does the bridge pattern allow us to do? |
Think of a component set where the components are
interchangeable. You can upgrade the speakers for deeper bass. If the
CD-player goes bad, we can similarly buy a new CD-player and plug it into the
Amplifier and we are good to go again.
The CD-Player (through the Amplifier) produces a signal
which is not useful to human beings. We still cannot hear the music. We can,
however, feed that signal into the speakers. The signal will then cause an
electro-magnet connected to the speaker’s cone to jump in tune with the music.
This jumping of the speaker’s cone causes it to collide with air molecules
surrounding it, thereby producing vibrations in the air which the human ear can
actually hear. Hence, we have an actual implementation.
In the bridge pattern there is an abstraction interface
which dictates that any class implementing it must be able to create an
abstraction of the work to be done. In this tutorial I call it an expression
of the work. In the example used, this is equivalent to a specification for CD-players
which will indicate that a CD-player must be able to produce a musical signal. Also
in the pattern are the refined abstractions. I imagine the GoF avoided the
term concrete-abstraction since it is a bit of an oxymoron. These
refined-abstractions are the actual classes which produce the expressions of
work. You could also think of them as the signal-producers. In the example of
the music system, these are CD-players, Tuners, Turntables, etc.
The bridge pattern also has an abstract implementer. This
is can be compared to a music-system specification for speakers which dictates
that the device must accept a musical signal and produce an audible vibration
based on that signal. Finally, the pattern includes the concrete implementers.
These are like the actual speakers or headphones. They accept the signal
produced by the CD-player and vibrate their cones rapidly to produce something
you can hear. In fact, 20 years from now when energy is at a premium and we
have to save every single watt (and they find a way to directly influence your
brain waves) you can expect that speakers will evolve to become tiny electrodes
which you clip on to your ears. The point is that there is flexibility in the
way the device works. As long as you “hear” the music then that is all that
matters because the music is “implemented.”
|
Intent seen through O/S & Applications |
VGA cards and monitors are a perfect example of the bridge
pattern. The refined abstractions are, of course, the VGA cards which produce
a signal. The Concrete implementers would naturally be the monitors that
display the graphics and text on screen. You can swap monitors, use LCD or
CRT, 15” or 17.” Similarly, you can swap out video cards and upgrade your (5
year old?) PCI card for an AGP card. They all work together quite well because
the abstraction and implementation layers have been split out and standardized
so that they talk to each other using a standard language. VGA cards produce a
standard signal. Monitors consume that standard signal and create a visual (or
concrete) implementation of the impression on their screen.
You will also realize that the CLR’s production of IL code
from your .NET program is the first half of the Bridge pattern (the abstraction
side). When the .NET framework is implemented on several Operating systems so
you can run your programs on any machine, then the .NET framework would
represent a complete use of the Bridge pattern. The JITTER in this case is the
abstraction side while the virtual machine which runs the code is the
implementation side.
|
Scenario - Sample Code |
Every hotel needs to know how satisfied its visitors are
with the various services. Naturally, the overall satisfaction the visitor
feels at the end of the vacation is a composite of how she felt about each of
the various experiences.
Now almost every trained teacher (no I am not one, I only
completed a few courses) knows about Howard Gardner who postulated that we have
various types of intelligences. He described 9 major intelligences that people
have, including linguistic, mathematical, kinesthetic, interpersonal
intelligence, intrapersonal intelligence and some others. So knowing all this,
we realize that people express themselves in various ways: some are visual,
some are numeric and some are spatial. We must therefore be able to give
visitors a variety of expressions which translate into a rating.
However, the visitor rates the experience and we must be
able to translate that rating into a standardized rating scale (which
represents a standard intermediate language). We know we have a standard
rating scale if in all rating modes, the highest rating translates to a common
maximum number and the lowest rating translates into a common minimum number. There
must also be an (perceptibly) even progression between minimum and maximum ratings
coming out of the rating control.
Now, as far as the implementation side is concerned, that
signal must be fed into a rating-renderer which gives a visually consistent
rendition of the rating regardless of the evaluator used to generate the
rating. We do this by feeding the 1-to-5 rating generated by the evaluators
into it. This gives it uniformity. The rating-renderer will convert this
rating into an actual visual representation by using a graphic object to
represent the rating.
UML–General
Figure 1
UML – Sample Code
Figure 2
|
Participants – Sample Code |
Abstraction - Evaluations
This is a governing interface which ensures that any class
which implements it can produce the expressions of work which are required. In
this case, the Evaluation MustInherit class ensures that all classes
implementing it can hold a reference to a concrete implementer and call its
method to produce the concrete results.
RefinedAbstraction – VerbalEvaluation,
SlidingEvaluation, NumericEvaluation
These contain a function which generates a signal indicating
the work to be done by the concrete implementation and also calls the
implementation function on the implementer object which it holds a reference
to, passing to it the signal which it has generated. In this case, it is a UI
control which captures an evaluation from the user and generates a rating value
to pass to the rating-renderer.
Implementation - RatingRenderer
An interacting interface which ensures that classes
implementing it can render the signal passed to it in a manner which completes
the work to be done (by displaying a graphical representation of the rating).
ConcreteImplementation – FaceRenderer,
StarRenderer, FishRenderer
This renders the signal into a tangible form which
represents completed work. In this sample code it renders the 1-to-5 rating in
a visual form which represents increasing levels of rating.
Containable-Object-Group 1
Abstraction
RefinedAbstractions
Containable-Object-Group 2
Implementation
ConcreteImplementations
|
Evaluation |
Internally it aggregates a control which the user will
manipulate in order to produce a range of scores. It also keeps an
acquaintance-reference to an Implementor (a renderer). The UpdateScoreValue()
function will call the render method on the Renderer and pass to it the score
which it should render at the same time.
Listing 1
' Equivalent to Abstraction in the Bridge pattern
Public MustInherit Class Evaluation
Protected m_RenderingDevice As Implementors.RatingRenderer
' Equivalent to Operation in the pattern
Public MustOverride Function UpdateScoreValue()
Public Property RatingRenderDevice() As
Implementors.RatingRenderer …
End Property
End Class
' Equivalent to Refined Abstraction in the Bridge pattern
Public Class VerbalEvaluation
Inherits Evaluation
Dim ChoiceList As ListBox
Dim ChoiceString As String
Dim EquivalentScore As Integer
Public Sub New(ByVal ControllerListBox As ListBox)
ChoiceList = ControllerListBox
End Sub
' Equivalent to Operation in the pattern
Public Overrides Function UpdateScoreValue()
ChoiceString = ChoiceList.SelectedItem
' Translate the specific selections in the list box to
'a standard language - the score
Select Case LCase(ChoiceString)
Case "poor"
EquivalentScore = 1
Case "fair"
EquivalentScore = 2
….
Case "excellent"
EquivalentScore = 5
End Select
' The payload statement – uses the acquaintance implementor
' to implement the score
m_RenderingDevice.RenderRating(EquivalentScore)
End Function
End Class
|
Renderers |
Accepts a score passed to it in the RenderRating function. It
will take that score and use its own technique to render that score into a
graphical image equivalent to the score. When the end-user clicks on each
group box (which contains a picture box) it creates a graphical image.
Listing 2
' Equivalent to Implementation in the Bridge pattern
Public MustInherit Class RatingRenderer
Protected RatingPictureBox As PictureBox
Protected RenderableImagesList As ImageList
Protected RenderingGraphics As Graphics
Public Sub New(ByVal RatingPicture As PictureBox, ByVal
ImageListToUse As ImageList)
RatingPictureBox = RatingPicture
RenderableImagesList = ImageListToUse
End Sub
Public Function ClearGraphicSurface()
RenderingGraphics = RatingPictureBox.CreateGraphics
RenderingGraphics.Clear(Color.FromKwnClr(KnownClr.Control))
End Function
' Equivalent to Implement in the Bridge pattern
Public MustOverride Function RenderRating(ByVal ScoreToRender
As Integer)
End Class
' Equivalent to Concrete Implementation in the Bridge pattern
Public Class FaceRenderer
Inherits RatingRenderer
Public Sub New(ByVal RatingPicture As PictureBox, ByVal
ImageListToUse As ImageList)
MyBase.New(RatingPicture, ImageListToUse)
End Sub
Public Overrides Function RenderRating(ByVal ScoreToRender As
Integer)
' Equivalent to Implement in the Bridge pattern
ClearGraphicSurface()
Dim GraphicWidth As Integer = 25
Dim GraphicHeight As Integer = 25
If ScoreToRender <> 0 Then
RenderableImagesList.Draw(RenderingGraphics, 50, 0,
GraphicWidth, GraphicHeight, ScoreToRender - 1)
End If
End Function
End Class
|
Client |
This will instantiate a default Abstractor (in this case a
VerbalEvaluation). It also configures the default renderer with a default Implementer
(in this case a Fish renderer). So the form is ready to go as soon as it
loads.
When the user clicks on an evaluation control (such as the track
bar, numericupdown or list box) the class which wraps that control becomes
instantiated by the client. That abstractor will now be responsible for
interpreting user inputs and converting them into abstractions (the score). In
order to fulfill the intent of the pattern, the form maintains a reference to
the Abstraction class AND the Implementer class. This means that whenever we
destroy one or the other, we can easily reconfigure the new class with the
existing one. The user never has to do two selections before seeing a
rendition of the rating.
We also perform a little trick when we are changing an implementer
class. In order to clear the picture box which is becoming inactive, we allow
the form to send an instruction to the implementer to render a “0” score (which
clears the graphic surface). This allows us to display just one graphical rendition
of the score at a time. This is done purely for aesthetic reasons.
Now ideally, we would also have a routine which disables
controls which are not active, so we would see only one evaluation at a time. However,
time does not permit this. The reader could attempt this implementation if
they so desire.
Listing 3
Private Sub frmRatingShell_Load(...)Handles MyBase.Load
Me.RenderingImplementor = New
Implementors.FishRenderer(Me.pbxFishes, Me.imlFish)
Me.AbstractEvaluator = New Abstractors.VerbalEvaluation
( Me.VerbalEval, Me.RenderingImplementor)
End Sub
Private Sub SlidingBar_Scroll(…) _
Handles SlidingBar.Scroll
ChangeEvaluator("Sliding")
AbstractEvaluator.UpdateScoreValue()
End Sub
Private Sub NumericEval_ValueChanged(…) _
Handles NumericEval.ValueChanged
ChangeEvaluator("Numeric")
AbstractEvaluator.UpdateScoreValue()
End Sub
Private Sub pbxFaces_Click( …) Handles pbxFaces.Click
ChangeRenderer("Faces")
End Sub
Private Sub pbxFishes_Click( … ) Handles pbxFishes.Click
ChangeRenderer("Fishes")
End Sub
Public Function ChangeEvaluator(ByVal EvaluatorType As String)
' This is a makeshift “factory-function” which instantiates
' the Abstractors for us
' We could just as easily have used Prototype Pattern
Me.AbstractEvaluator = Nothing
Select Case EvaluatorType
Case "Verbal"
AbstractEvaluator = New Abstractors.VerbalEvaluation
(VerbalEval, RenderingImplementor)
Case "Sliding"
AbstractEvaluator = New Abstractors.SlidingEvaluation _
(SlidingBar, RenderingImplementor)
Case "Numeric"
AbstractEvaluator = New Abstractors.NumericEvaluation
(NumericEval, RenderingImplementor)
End Select
AbstractEvaluator.UpdateScoreValue()
End Function
Public Function ChangeRenderer(ByVal RendererType As String)
' Clears the graphic surface of the Implementor we are
' decomissioning
Me.RenderingImplementor.RenderRating(0)
Me.RenderingImplementor = Nothing
' This statement-block figures out which implementor to
' instantiate.
' It assigns the new implementor to the Parent container
' which is the form
' so that even when we destroy the Abstractor,
' the implementor is kept alive
' and we can commission it back into use by assigning it to
' the new Abstractor
Select Case RendererType
Case "Faces"
Me.RenderingImplementor = New Implementors.FaceRenderer
(Me.pbxFaces, Me.imlFace)
Case "Stars"
Me.RenderingImplementor = New Implementors.StarRenderer
(Me.pbxStars, Me.ImlStar)
End Select
' Takes the reference to the implementor which the Form keeps
' and assigns it to the new Abstractor
Me.AbstractEvaluator.RatingRenderDevice = _
Me.RenderingImplementor
AbstractEvaluator.UpdateScoreValue()
End Function
End Class
Opportunities for and Costs of - Adaptation and
Extension
Adaptation is easy in the bridge pattern. Modification of a
refined abstraction or a concrete implementation requires absolutely no testing
of the other classes in the system. That is unless you do something like (in
this case) changing the rating scale which the class uses. This would force
you to revise all the classes throughout the system. That is more than just a
change to a class, right? If you look at the pattern tweaking section, you
will see a discussion on the expansion of the bridge pattern which includes the
incorporation of a signal class and the way in which it ensures that
programmers do not mistakenly change the outputs of refined abstractions or the
inputs to concrete implementers in an arbitrary manner in the course of
maintaining the abstraction/implementation classes.
So unexpected blunders aside, we can maintain abstractions
and implementations in a very discrete way. The same way we could change
almost any wiper-blade in a wiper without affecting the wiper motor (another
example of the bridge system in real life).
In the same vein, we can add more abstractions or
implementations without affecting the others in the system. There is nothing
we have to do to register new abstractions/implementations either; we simply
make use of them in the client. The client simply instantiates the new
implementation and configures a new abstraction with it, calling the relevant
operation on the abstraction to do the work required.
|
Interface issues |
There are two interfaces in the pattern.
The abstraction is a governing interface which tends to be a
must-inherit or abstract-class. It ensures that refined abstractions hold a
reference to the concrete implementation. It also ensures that they implement
the abstract operation.
The implementation is an interacting interface which ensures
that we can swap implementations at will without causing any hiccups.
Namespace/Scope/Accessor issues
Coding a public property into the abstraction which sets the
implementer being used allows you to change implementers on the fly.
Listing 4
Public MustInherit Class Evaluation
…
Public Property RatingRenderDevice() As Implementors.RatingRenderer
…
Set(ByVal Value As Implementors.RatingRenderer)
RenderingDevice = Value
End Set
End Property
|
Common mistakes in Pattern Literature / Pitfalls to avoid |
See discussion above on public property for setting implementer
used by abstraction.
Being a structurally simple pattern, there is not much
opportunity to get it wrong. However, I have seen a couple of problems in some
implementations.
The operation which the refined abstractions should perform
should be in the interface/abstract-class with a MustOverride designation. It
should not be optional that the refined abstraction implements it or implements
it with its own unique method signature. By leaving it out at the interface
level, you leave it up to the programmer who develops the code for each refined
abstraction to decide how to implement it or whether or not to. You lose the
robustness and pattern-safety when you do that.
The other issue is that the Bridge pattern (contrary to its
name) is all about action. I do not believe that a bridge pattern properly
applies to an abstraction or implementation which does not have methods which
act hand in hand. If one object-hierarchy (or COG) is state-based and the
other is method-based, then I do believe that you should take a second look at
the flyweight pattern to see whether it fits your needs better.
Finally, when teaching or demonstrating the Bridge pattern,
you should be sure to highlight the common signal language which should be
spoken by both abstractions and implementations. This brings out the pattern
more forcefully. Also, be careful to demonstrate several concrete
implementation and several refined abstractions so that your audience gets the
idea that both abstractions and implementations are interchangeable (within
their own type). Otherwise, your students may liken the pattern to a screw-driver-handle
with swappable screw-driver heads, which is a weakening of the pattern.
|
Advanced Issues |
Pattern Tweaking
While I agree with the GoF that the Bridge is a very useful
pattern and definitely deserves a place among the patterns, I am not sure I
like their internal arrangement of the pattern.
First of all, I think the name is not the best one to
describe the pattern. I prefer to call it the Drill or Draw-Bridge pattern. You
see, in line with the intent and operation of the pattern, Abstraction-classes
should be in their own COG. Implementation classes should also be in their own
COG.
In a drill we have several speed settings along with
reversible rotation of the motor. This in effect gives us interchangeable
motor behaviours. In the case of the drill-bits, we can swap them and use
concrete-bits or metal-bits. We can also change the bit-size. So we have extensibility
and adaptability in two dimensions: motor operation and drill-bit behavior.
Based on the discussion above, we could have similarly named
the pattern accelerator/gear-stick since we vary engine speed with gas and vary
the drive-ratio with the gear-stick.
As implemented in GoF, the master class is the Abstraction
class which aggregates Implementations. To me, this feels a bit like having a
bet with someone and letting them hold both bets. To overcome this shortfall,
I would also introduce a new master class called the shell or container class
which holds on to both the abstraction and implementation classes. In this way
they are both relegated to being helper-classes as the pattern intended. I
would also include two more classes within the shell class. These would be a
switch class and a signal class. Work would then be accomplished by configuring
the shell-class with an abstraction class (CD-player/turn-table or
motor-speed-fast/motor-speed-slow) and implementation class (speaker/headphone
or concrete-bit/metal-bit). You also need to set the switch-class within the
shell class to a position (volume-high/volume-low or
forward-rotation/reverse-rotation). The Bridge-shell class would then instruct
the Abstraction class to set the signal properties of the signal class. The
Implementation class would then sample the signal class and translate the
signal into work.
The code below is a bit rough-hewn and would certainly be
subject to a lot of refinement, but off the top of my head it might look
something like the following.
Class Abstraction_Motor
Private Function Abstract()
Private MotorSpeed As Integer
'Represents the signal Class which is the intermediary between
' the abstraction and implementation.
Private Gear
To make this work really nicely, we could probably implement
the whole thing with events where the Abstraction-class raises an event when it
performs an action or changes its state. This would then trigger an updating
of state in the signal class (which temporarily stores outputs of the
abstraction-class or the results of its work). Change in the signal class
could then trigger a change in the event-listeners which would be the
implementation class. This would take the state of the signal class and do some
useful work in its implementation method.
Listing 5
Function Spin(ByVal Implementation As Drill_Bit)
' Make the Drill bit spin fast
End Function
End Class
Class Implementation_ DrillBit
Private Function Implement()
End Class
Class Bridge_Drill
Dim Inner_Motor As Abstract_Motor
Dim Projecting_DrillBit As Implementation_DrillBit
Dim m_speedSetting As Integer
Public Property SpeedSetting() 'Can be set independently
Set(ByVal Value)
InnerMotor.MotorSpeed = value
End Set
End Property
Public Property DrillBit()
Drillbit = value
End set
End Property
Public Property InnerMotor()
InnerMotor = value
End set
End Property
Function DoWork_MakeHole()
Inner_Motor.Spin(Projecting_DrillBit)
End Function
End Class
Dim Motor As New Abstraction_Motor
Dim DrillBit As New Implementation_DrillBit
' Client Code to make the bridge do some useful work.
Dim Drill As New Bridge_Drill(Motor, DrillBit)
Drill.DoWork_MakeHole
|
Downloads |
|
Summary |
In this article you have learned the application of Bridge
pattern with the help of a sample Visual Basic .NET application.
|
|
|
User Comments
No comments posted yet.
|
Product Spotlight
|
|