I use this pattern so often, it's simply ingrained in my head! The bridge pattern allows you to have an abstract representation of a class which you can easily add new "targets" to. So let's say you were going to do something crazy like write a cross-platform toolkit for users to use. You want to have a PushButton object that someone can use without them having to care whether they're running on Windows, Linux or the Mac (sound familiar?). The user should just be able to say "give me a PushButton", and they are given one for the appropriate platform they're running on.
The way to accomplish this magic is via the bridge (and to a very small extent, factory) pattern. The way a bridge works is by abstracting the implementation details of the class to a level where it's useable for the end user. Then build a bridge over to a concrete implementation.
So let's build a hypothetical CoolSystem object which we want to use to do things like get the double-click time on a system as a demonstration.
First, let's define the CoolSystem class itself. We're going to define it as a module instead of a class simply because the user will only ever need one instance.
[rbcode]
Module CoolSystem
Dim mImp as CoolSystemImp
Dim mInitialized as Boolean
Private Sub Initialize()
End Sub
Function GetDoubleClickTime() as Integer
End Function
End Module
[/rbcode]
The Initialize method and mInitialized property are there as a way for us to initalize the module property when the user first tries to call one of our functions. If this were a class instead of a module, we could call it "Constructor" instead of "Initialize".
The CoolSystem module's purpose in life is to hold onto the proper implementation (depending on the platform) of our bridge. This way, the user doesn't have to care whether they are using a Macintosh CoolSystem, or Windows CoolSystem, etc. They just use the module, and the module takes care of things itself.
So that CoolSystemImp property that we've defined is the property which holds our bridge. It is defined in generic terms so that we can use any bridge that suits our needs. Let's define that interface so you understand a little better.
[rbcode]
Interface CoolSystemImp
Function GetDoubleClickTime() as Integer
End Function
End Interface
[/rbcode]
This looks quite familar, doesn't it? That's because the implementors of the CoolSystemImp interface are going to be doing all the work for us. So let's see what a few implementations looks like.
[rbcode]
Class CoolSystemImpWin32 Implements CoolSystemImp
Function GetDoubleClickTime() as Integer
#if TargetWin32
Declare Function MyGetDoubleClickTime Lib "User32" Alias "GetDoubleClickTime" () as Integer
return MyGetDoubleClickTime
#endif
End Function
End Class
Class CoolSystemImpMacCarbon Implements CoolSystemImp
Function GetDoubleClickTime() as Integer
#if TargetCarbon
Declare Function GetDblTime Lib "CarbonLib" as Integer
return GetDblTime
#endif
End Function
End Class
Class CoolSystemImpMacClassic Implements CoolSystemImp
Function GetDoubleClickTime() as Integer
#if TargetPPC
Declare Function GetDblTime Lib "InterfaceLib" as Integer
return GetDblTime
#endif
End Function
End Class
[/rbcode]
So now that we have some of the implementations that we want to use, let's see the final step: hooking our bridge up properly. We are going to make the Initialize method take care of the heavy lifting for us by putting it in charge of figuring out which bridge to use. Then we need to make our functions call through the bridge and we're done! So here's our new CoolSystem module:
[rbcode]
Module CoolSystem
Dim mImp as CoolSystemImp
Dim mInitialized as Boolean
Private Sub Initialize()
// If we've already done all this, bail out
if mInitialized then return
// We're now initalized properly
mInitialized = true
// Figure out which bridge to call
#if TargetWin32
mImp = new CoolSystemImpWin32
#elseif TargetCarbon
mImp = new CoolSystemImpMacCarbon
#elseif TargetPPC // misnomer! This really means Mac Classic
mImp = new CoolSystemImpMacClassic
#endif
End Sub
Function GetDoubleClickTime() as Integer
// Make sure that we're all set up properly
Initialize
// Call thru to the bridge
return mImp.GetDoubleClickTime
End Function
End Module
[/rbcode]
Ta da! You've just seen how we (at REAL Software) implement a number of our framework classes (such as FolderItem, TCPSocket, UDPSocket and many others) using the Bridge Pattern. As you can see, this pattern comes in really handy when you have a handful of "different" implementations that all perform the same function. You can add new implementations very easily (simply make a new Imp implementor and hook it in to the Initialize method) without having to worry the end-user at all.
totally off topic
Where has RealGuru's gone?
Site was hijacked because Tonio was too lazy to secure it after the last time it was hijacked.
:(
Yep I was a bit surprised at what *popped* up instead!
Back on-topic:
Once again - Well Done! ::hearty round of applause::
Besides who needs realgurus when we have the guru himself dispensing tidbits of wisdom on a nearly daily basis...
~joe
:)