AspAlliance.com LogoASPAlliance: Articles, reviews, and samples for .NET Developers
URL:
http://aspalliance.com/articleViewer.aspx?aId=994&pId=-1
Understanding Bridge Pattern using .NET
page
by David Simmonds
Feedback
Average Rating: This article has not yet been rated.
Views (Total / Last 10 Days): 47055/ 81

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.


Product Spotlight
Product Spotlight 

©Copyright 1998-2024 ASPAlliance.com  |  Page Processed at 2024-03-28 7:51:08 AM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search