VFP Design Pattern - Bridge

By Steven Black

Context

You are designing a new class, or adapting an existing class to make it more reusable. You anticipate future extensions to object's form or function and you want to avoid extending a class hierarchy for the sake of new implementations. perhaps you'd like to choose a specific behavior at run-time.

Problem

To achieve reuse, some classes need to adapt either their implementations, their interfaces, or both. How to structure a class so that either (or both) can be easily modified?

Forces

  • The existing codebase may constrain what we can do with a class's implementation.
  • Reuse could mean reuse by other systems that may expect or require a different interface than the one currently provided by our existing class.
  • We may need to adapt a class's interface, implementation, or both.

Solution

Decouple an object's public interface ("form") from its implementation (its "function"), using two (or more) objects where otherwise one might less flexibly suffice.

Separate a class's interface from its implementation by making each a separate but tightly coupled objects. This way each can be varied independently of the other.

The workings among the players in a Bridge should be direct and simple: the interface object forwards client requests to the appropriate implementation object. Tight coupling within the Bridge structure is both common and desirable. The tight coupling is much easier to manage when all the implementation objects come from the same class, as illustrated in the Codebook example illustrated below.

Resulting Context

A Bridge is a decoupling of an abstraction (interface) from its implementation so the two can vary independently. Bridges are scale-independent structures that apply in many object oriented situations. A Bridge also serves as an atomic element player in other design patterns.

Rationale

The usual way to manage the requirement for different interface or implementation is by extending the class hierarchy using inheritance. After all, inheritance is usually the first mechanism most folks use to reuse their classes. And why not? In the early going, inheritance is always a big success.

At the limit, however, inheritance leads to sclerosis -- inheritance doesn't scale particularly well. Simple, single-object inheritance binds interface and implementation - you only have one package to transform into a subclass. This means that interface and implementation aspects cannot be independently varied without extending the class hierarchy.

But class hierarchies naturally loses their inherent adaptability with size and as the number and variety of clients increases. Why? -- side effects, partly. The probability of change side-effects in clients increases with the number and variety of those clients, and correspondingly with the current state of the hierarchy. In a giant class hierarchy, making a small change near the top can potentially be very expensive. This will occasionally limit what can be reasonably done at a given level in a class hierarchy.

The Bridge pattern mitigates class sclerosis by decoupling interfaces from implementations, and putting the abstraction and implementation in separate class hierarchies. This allows the interface and implementation classes to vary independently, and the inherent substitutability of subclasses makes the structure intrinsically adaptable (and hence reusable). The Bridge in all this is the relationship between the interface and the implementation objects that together form a self-supporting system.

A.K.A.

"Handle And Body" and "Envelope And Letter". Those descriptive names are good - the dual-object nature of Bridge patterns is well conveyed, as is the tight coupling usually found between the programming interface and implementation objects.

Visual FoxPro Samples

Here we'll look at three simple ways Bridge patterns can be built in FoxPro: First with member properties, second we'll do more flexible implementations using member arrays, and third using object composition within containers.

Member Property Bridge

One way to build a Bridge is as diagrammed below. Here an interface member property contains a reference to an implementation object.

Figure 1. Bridge pattern where an interface class member property points to an implementation class. The Bridge in this diagram is .oImp, the member property that stores a reference to an implementation object - that's physically what the arrow represents. The benefit is this: each side can be subclassed independently of the other.

In general terms, here's how such systems are constructed in VFP. What follows is a simple illustrative class that provides WAIT WINDOW services.

XX= CREATEOBJECT( "WaitMsgServer")
XX.Execute("This, for now, is in a WAIT WINDOW")

DEFINE CLASS WaitMsgServer AS MsgInterface
	FUNCTION Init( txPassed)
	*-- Load an interface object
	THIS.aImp= CREATEOBJECT("WaitMsg")
	ENDFUNC

	FUNCTION Execute( txPassed)
	*-- Pass the request along...
	THIS.aImp.Execute( txPassed)
	ENDFUNC
ENDDEFINE

DEFINE CLASS WaitMsg AS MsgImplementation
	FUNCTION Show( txPara1)
	WAIT WINDOW THIS.cMessage
	ENDFUNC
ENDDEFINE

DEFINE CLASS MsgInterface AS CUSTOM
	*-- Abstract message interface class
	aImp= .NULL.
	FUNCTION Execute( txPassed)
	*-- Abstract
	ENDFUNC
ENDDEFINE

DEFINE Class MsgImplementation AS Custom
	*-- Abstract message implementation class
	cMessage= ''
	FUNCTION Execute( tcPassed)
	THIS.cMessage=tcPassed
	THIS.Show()
	ENDFUNC
	FUNCTION Show
	*-- Abstract
	ENDFUNC
ENDDEFINE

Sample 1. A simple Bridge using a member property to reference the implementation object.

Another example of this sort of Bridge is found in Codebook where the cBizobj (business object) class uses an object of the cDataBehavior class to implement the usual table navigation and record-processing functions. The following diagram illustrates this.

Figure 2. This Bridge pattern in Codebook 3.0 hangs on an aptly named member property. Any of the cDataBehavior subclasses may be substituted.

Member Array Bridge

Starting from the Member Property Bridge, it's a short stretch to provide multiple simultaneous implementations by using multiple member properties or, as described below, member arrays.

Figure 3. A flexible Bridge: a selection of implementations stored in a member array.

Code sample 1 is modified here to support four different types of dialogs to extend the legacy WAIT WINDOW capability.

DEFINE CLASS WaitMsgServer AS MsgInterface
	FUNCTION Init
	*-- Load interface objects
	THIS.aImp[1]= CREATEOBJECT("WaitMsg")
	THIS.aImp[2]= CREATEOBJECT("RegularMsg")
	THIS.aImp[3]= CREATEOBJECT("InfoMsg")
	THIS.aImp[4]= CREATEOBJECT("WarningMsg")
	THIS.aImp[5]= CREATEOBJECT("ErrorMsg")
	*-- Supporting the legacy singular WaitMsg capability
	THIS.oImp= aImp[1]
	ENDFUNC

	FUNCTION Execute( txPassed, tnMessageType)
	THIS.aImp[tnMessageType].Execute( txPassed)
	ENDFUNC
ENDDEFINE

DEFINE CLASS WaitMsg AS MsgImplementation
	FUNCTION SHOW( txPara1)
	WAIT WINDOW THIS.cMessage
	ENDFUNC
ENDDEFINE

DEFINE CLASS RegularMsg AS MsgBoxImplementation
	cTitle= "My Application"
ENDDEFINE

DEFINE CLASS InfoMsg AS RegularMsg
	nIcon= 64
ENDDEFINE

DEFINE CLASS WarningMsg AS InfoMsg
	nIcon= 48
ENDDEFINE

DEFINE CLASS ErrorMsg AS WarningMsg
	nIcon= 16
	nButtons= 5
ENDDEFINE

DEFINE CLASS MsgInterface AS CUSTOM
	*-- Abstract message interface class
	DIMENSION aImp[4]
	aImp[1]= .NULL.
	aImp[2]= .NULL.
	aImp[3]= .NULL.
	aImp[4]= .NULL.
	*-- Virtual

	FUNCTION Execute( txPassed)
	ENDFUNC

ENDDEFINE

DEFINE CLASS MsgBoxImplementation AS MsgImplementation
	nIcon= 0
	nButtons= 0
	FUNCTION Show
	=MessageBox( THIS.ctext, THIS.nIcon+THIS.nButtons, THIS.cTitle)
ENDFUNC
ENDDEFINE

DEFINE Class MsgImplementation AS Custom
	*-- Abstract message implementation class
	cMessage= ''

	FUNCTION Execute( tcPassed)
	THIS.cMessage=tcPassed
	THIS.show()
	ENDFUNC

	*-- Virtual
	FUNCTION Show
	ENDFUNC
ENDDEFINE

Sample 2. Many implementations can be connected to an interface array member property.

Containership Bridge

Containership implementations are similar to array member implementations. In Visual FoxPro, containership is superbly implemented, so containership Bridges are easy to create and manage. Useful are the Controls() array, the PARENT keyword, SetAll(), and AMEMBERS(,,2), which make it possible to manage containership nesting.

The containership relationship is illustrated below, followed by an illustrative code example.

Figure 4. An interface container contains one or many implementations.

DEFINE CLASS MsgServer AS MsgInterface
	FUNCTION Init
	*-- Load an interface object with implementations.
	*-- Exercise for the reader: Imagine delaying
	*-- the AddObject calls until actually needed.
	THIS.AddObject( "msgWaitWindow","WaitMsg")
	THIS.AddObject( "msgRegular", "RegularMsg")
	THIS.AddObject( "msgInfo", "InfoMsg")
	THIS.AddObject( "msgWarning", "WarningMsg")
	THIS.AddObject( "msgError", "ErrorMsg")
	ENDFUNC

	FUNCTION Execute( tnPassed, tcMessage)
	*? I don't recommend doing it quite like this --
	*? This simple example assumes you know the number of the
	*? implementation object. In a perfect but (less concise)
	*? example, .Execute(x,y) could accept an x of type "C", as in
	*? <whatever_message_server>.Execute("Warning", <Message>)
	*?
	THIS.Controls(tnPassed).Execute(tcMessage)
	ENDFUNC
ENDDEFINE

DEFINE CLASS WaitMsg AS MsgImplementation
	FUNCTION SHOW( txPara1)
	WAIT WINDOW THIS.cMessage
	ENDFUNC
ENDDEFINE

DEFINE CLASS RegularMsg AS MsgBoxImplementation
	cTitle= "My Application"
ENDDEFINE

DEFINE CLASS InfoMsg AS RegularMsg
	nIcon= 64
ENDDEFINE

DEFINE CLASS WarningMsg AS InfoMsg
	nIcon= 48
ENDDEFINE

DEFINE CLASS ErrorMsg AS WarningMsg
nIcon= 16
nButtons= 5
ENDDEFINE
 DEFINE CLASS MsgInterface AS Container
	*-- Abstract message interface class
	*? Hardcoded dimension
	DIMENSION aImp[4]
	aImp[1]= .NULL.
	aImp[2]= .NULL.
	aImp[3]= .NULL.
	aImp[4]= .NULL.
	FUNCTION Execute( txPassed)
	*-- Abstract method
	RETURN 0
	ENDFUNC
ENDDEFINE

DEFINE Class MsgImplementation AS Custom
	*-- Abstract message implementation class
	cMessage= ''

	FUNCTION Execute( tcPassed)
	THIS.cMessage=tcPassed
	THIS.Show()
	ENDFUNC

	FUNCTION Show
	*-- Abstract method
	RETURN 0
	ENDFUNC
ENDDEFINE

DEFINE CLASS MsgBoxImplementation AS MsgImplementation
	nIcon= 0
	nButtons= 0
	FUNCTION Show
	=MessageBox( THIS.ctext, THIS.nIcon+THIS.nButtons, THIS.cTitle)
	ENDFUNC
ENDDEFINE

Sample 3. Multiple implementations instantiated in an interface container