As promised, I am starting a little series on Design Patterns, the first one to look at is the “Bridge” – ofter referred to as the “mother of all design patterns” because it is the most basic of all patterns and you will discover, as you become more familiar with patterns in general, that you keep finding the bridge (in some form) at the bottom of almost every other pattern.
OK, so what is a bridge?
The forma definition of the bridge, as given in “Design Patterns, Elements of Reusable Object-Oriented Software” by Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides is that a Bridge:
Decouples an abstraction from its implementation so that the two can vary independently
Impressive eh? Do we really do that in our code? The short answer is that we probably should do so more often than we realize. Let us take a common example.
When writing code in our forms and classes we naturally want to trap for things going wrong and usually (being considerate developers) we want to tell the user when that happens. The most obvious, and simplest, solution is to include a couple of lines of code in the appropriate method. The result might look something like this:
IF NOT <A Function that returns True/False>
lcText = "Check failed to return the correct value" + CHR(13)
lcText = lcText + "Press any key to re-enter the value"
WAIT lcText WINDOW
RETURN .F.
ENDIF
In our entire application we may well have dozens of situations where we display a wait window like this to keep the user posted as to what is going on and this works perfectly well until one of two things happens. Either our users decide that they really hate these pesky little “wait windows” and would much prefer a windows-style message box, or, worse, we need to deploy the code in an environment that simply doesn’t support the “wait window”. Maybe as a COM component, or in a web form. Now we must go and hunt through our application and find every occurrence of this code and change it to support the new requirement. I don’t know about you, but in my experience the chances of getting such a task right first time (not missing any occurrences and re-coding every one perfectly) are so close to zero as to be indistinguishable from it. Even if we could be confident of doing it, we still have the whole issue of testing it to deal with.
Another scenario arises when we are working with colleagues. Suppose in one of my forms I call on a system function and, being a careful developer, I test to ensure that the result is what I expected and, in case of error, code up a message like this:
lcText = “Incorrect value Returned from the Function”
Meanwhile my co-worker, writing a similar piece of code in another part of the application, codes their test of the same function call’s result it as:
lcText = “The requested task could not be completed”
So even if we both show the text in the same way, the actual content is different, even though to the end user, the same thing is happening! Confusing? You bet it is. Worse, it doesn’t look very polished to the end user.
So what has this to do with the Bridge pattern? Well, the reason that we have this problem is because we failed to recognize that we were coupling an abstraction (displaying a message to the user) to its implementation (the “Wait Window”, or “MessageBox”). Had we done so we might have used a bridge instead and then we could have avoided the problem. Here’s how the same code would look if we had implemented it using a bridge pattern:
IF NOT <A Function that returns True/False>
This.oMsgHandler.ShowMessage( 9011 )
RETURN .F.
ENDIF
See the difference? We no longer know, or care, what message is actually going to be displayed, or how it is going to be handled All that we need to know is where to get a reference to the object which is going to handle the message for us, and the identifier that tells the handler which message we wish to display. Of course, it is a fundamental requirement that all possible handlers will implement the appropriate interface, in this case a ShowMessage() method.
It is that source of the reference that is the “bridge”. In this example the “oMsgHandler” property provides the bridge between the code that requires a message and the mechanism for dealing with a message. Now, all that is needed to change the way in which our message is handled is to change the object reference stored in that property. That is something that can easily be done at run time depending on the environment in which the parent object has been instantiated (and the mechanism for doing that is an example of another pattern, named “Strategy”, which we’ll cover later). This approach successfully de-couples the abstraction from its implementation and our code is much more re-usable as a result.
What are the components of a bridge?
A bridge has two essential components, the “abstraction”, which is the object responsible for initiating an operation, and the “implementation” which is the object that carries it out. The abstraction knows its implementation either because it holds a reference to it, or because it owns (i.e. contains) it. Note although it often happens that the abstraction creates the implementation, it is not an absolute requirement of the pattern. A bridge could also make use of a pre-existing reference to an implementation object. In fact designing bridges this way can be very efficient because different abstraction objects can utilize the same implementation object. In fact, since Visual FoxPro has a very good containership model most developers find that they have used it (unintentionally) to implement Bridges more often than they realize. Most 'manager objects' (e.g. Form Manager, Ini File Manager, Message Manager) are actually examples of the Bridge Pattern.
However, don’t confuse message forwarding with a bridge. If a method of a command button calls a method on its parent form that directly implements the necessary action, that is message forwarding, not a bridge. However, if that form method were to call another object to carry out the action, then we would have a bridge.
Interestingly another possibility occurs when a form (or other container) is maintaining a property that holds a reference to an implementation object. A contained object (e.g. a command button on a form) may access that property directly and get a local reference to the implementation object. It can then call methods on the implementation object directly. Now, is this a bridge, or message forwarding? In fact you could probably argue either side of the case with equal justification. However, the answer doesn’t really matter. This issue only arises because Visual FoxPro exposes properties as “Public” by default, and also implements back pointers which allow objects to address their parent directly. In most Object Oriented environments it is simply not possible for objects to behave so rudely toward each other.
So to implement a bridge you need to identify which object is going to play the role of the abstraction, and which the implementation. Once you have them defined, all that is left is to decide on how the bridge between the two is going to be coded and that will depend entirely on the scenario.
Published Sunday, December 10, 2006 2:26 PM by andykr
No comments:
Post a Comment