This is an overview of selected object-oriented design patterns, and how to build them in Visual FoxPro. The patterns exposed here are:
At the outset, the goal of this paper is to introduce implementations of object oriented design patterns to the FoxPro community. Hopefully it will serve to stimulate some productive discussion. Why? Three reasons: Firstly because patterns help us illustrate object usage and programming style. Secondly, understanding the give-and-take among patterns helps to avoid silly tactical mistakes. Third: knowledge of how design patterns work together is a pre-requisite to become an architect in any field!
Pattern language is a vast topic, and much of it is beyond the scope of this paper. We wont dwell on many significant aspects of pattern semantics. Suffice here to say that software patterns exist, here are a few of them, and heres how to rig them in Visual FoxPro.
Want to learn more on object-oriented design patterns? A bibliography, interesting reading, is included near the end of this document. In fact, this paper arises because a common question I hear from colleagues is "These are evocative concepts, but how can I implement these designs in Visual FoxPro?"
The graphic representations of classes and their interaction is easier if we understand the object-oriented notation for which, unfortunately, there is no common standard. The class and object diagrams herein are in based on omt (Object Modeling Technique) pioneered by Rumbaugh with a few twists, hopefully for clarity.
Figure 1 shows diagrams for abstract and concrete classes. The class name is in bold type at the top of the box. Key operations, suffixed with (), and the names of instance variables, then appear below.
Figure 1. Abstract and concrete classes
Object relationships are depicted with arrows and lines, as shown in Figure 2, and show the following types of interrelations:
Inheritance is depicted with a large triangle. Here FaxServer and CommPortServer are subclasses of the Output class.
Instantiation is shown with a dashed arrow, here meaning that CreationTool instantiates FaxServer.
Part-of or aggregation relationships are shown with arrows with diamond bases. Here the OutputManager is composed of, among other things, one or many objects of the Output class.
Referencing is depicted with arrows: The FaxServer keeps one reference to CommPortServer, and OutputManager keeps one (arrowhead) or many (filled circle) objects of the Output class.
Figure 2. Object relationships
Pseudocode examples appear in pseudo-edit windows.
Figure 3. Pseudocode expansion.
Bridge patterns split interfaces from implementations so the two can vary independently. bridge structures are objects composed of interface and implementation sub-objects, and the more flexibly the better. The workings should be direct and simple: the interface object forwards client requests to the appropriate implementation object.
Look for bridge patterns when hiding implementation details, wherever extensibility is required, and always when an object interface and its implementation are separate. The bridge is pattern is independent of scale and found nearly everywhere: encapsulated objects, systems of objects, even entire frameworks can be modeled on bridge.
bridge is fittingly classified as a Structural Pattern. Other common names for bridge are evelope and letter and handle-body. Those descriptive names are good the dual-object nature of bridge patterns is well conveyed, as is the rather tightly coupled interface usually found between them.
Here well look at three simple ways bridge patterns can be built in FoxPro: First with member properties, second well do more flexible implementations using member arrays, and third using object composition within containers.
Member Property bridge
Figure 4. Bridge pattern where a member property points to an implementation. Each can be subclassed independently of the other.
One way to build a bridge is as diagrammed in Figure 4. Here an interface member points to an Implementation.
In general terms, heres how such systems are constructed in VFP. What follows is a simple illustrative class that provides WAIT WINDOW services.
XX= CREATEOBJECT( "WaitMessageServer")
XX.Execute("This, for now, is in a WAIT WINDOW")
DEFINE CLASS WaitMessageServer AS MessageInterface
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 MessageImplementation
FUNCTION Show( txPara1)
WAIT WINDOW THIS.cMessage
ENDFUNC
ENDDEFINE
DEFINE CLASS MessageInterface AS CUSTOM
*-- Abstract message interface class
aImp= .NULL.
FUNCTION Execute( txPassed)
ENDFUNC
ENDDEFINE
DEFINE Class MessageImplementation AS Custom
*-- Abstract message implementation class
cMessage=
FUNCTION Execute( tcPassed)
THIS.cMessage=tcPassed
THIS.Show()
ENDFUNC
FUNCTION Show
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 5. 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, its a short stretch to provide multiple simultaneous implementations by using multiple member properties or, as described below, member arrays.
Figure 6. 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 WaitMessageServer AS MessageInterface
FUNCTION Init
*-- Load interface objects
THIS.aImp[1]= CREATEOBJECT("WaitMsg")
THIS.aImp[2]= CREATEOBJECT("RegularMessage")
THIS.aImp[3]= CREATEOBJECT("InfoMessage")
THIS.aImp[4]= CREATEOBJECT("WarningMessage")
THIS.aImp[5]= CREATEOBJECT("ErrorMessage")
*-- 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 MessageImplementation
FUNCTION SHOW( txPara1)
WAIT WINDOW THIS.cMessage
ENDFUNC
ENDDEFINE
DEFINE CLASS RegularMessage AS MessageBoxImplementation
cTitle= "My Application"
ENDDEFINE
DEFINE CLASS InfoMessage AS RegularMessage
nIcon= 64
ENDDEFINE
DEFINE CLASS WarningMessage AS InfoMessage
nIcon= 48
ENDDEFINE
DEFINE CLASS ErrorMessage AS WarningMessage
nIcon= 16
nButtons= 5
ENDDEFINE
DEFINE CLASS MessageInterface 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)
ENDDEFINE
DEFINE CLASS MessageBoxImplementation AS MessageImplementation
nIcon= 0
nButtons= 0
FUNCTION Show
=MessageBox( THIS.ctext, THIS.nIcon+THIS.nButtons, THIS.cTitle)
ENDFUNC
ENDDEFINE
DEFINE Class MessageImplementation 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 bridge implementations are similar to array member bridge implementations except that, in FoxPro, containership management is superbly implemented. The Controls() array, PARENT keyword, SetAll(), and AMEMBERS(,,2) make it easy to manage any depth of containership nesting.
The containership relationship is illustrated below, followed by an illustrative code example.
Figure 7. An interface container contains one or many implementations.
DEFINE CLASS MessageServer AS MessageInterface
FUNCTION Init
*-- Load an interface object
THIS.AddObject( "msgWaitWindow","WaitMsg")
THIS.AddObject( "msgRegular", "RegularMessage")
THIS.AddObject( "msgInfo", "InfoMessage")
THIS.AddObject( "msgWarning", "WarningMessage")
THIS.AddObject( "msgError", "ErrorMessage")
ENDFUNC
FUNCTION Execute( txPassed, tnMessageType)
*? I wouldnt normally recommend doing it quite like this
THIS.Controls(txPassed).Execute( txPassed)
ENDFUNC
ENDDEFINE
DEFINE CLASS WaitMsg AS MessageImplementation
FUNCTION SHOW( txPara1)
WAIT WINDOW THIS.cMessage
ENDFUNC
ENDDEFINE
DEFINE CLASS RegularMessage AS MessageBoxImplementation
cTitle= "My Application"
ENDDEFINE
DEFINE CLASS InfoMessage AS RegularMessage
nIcon= 64
ENDDEFINE
DEFINE CLASS WarningMessage AS InfoMessage
nIcon= 48
ENDDEFINE
DEFINE CLASS ErrorMessage AS WarningMessage
nIcon= 16
nButtons= 5
ENDDEFINE
DEFINE CLASS MessageInterface 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)
RETURN 0
ENDFUNC
ENDDEFINE
DEFINE Class MessageImplementation AS Custom
*-- Abstract message implementation class
cMessage= ''
FUNCTION Execute( tcPassed)
THIS.cMessage=tcPassed
THIS.Show()
ENDFUNC
FUNCTION Show
RETURN 0
ENDFUNC
ENDDEFINE
DEFINE CLASS MessageBoxImplementation AS MessageImplementation
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.
On balance, classes built as bridges are easier to extend and adapt since their interfaces and implementations can be subclassed independently. Even though interfaces and implementations are decoupled, a high degree of cohesion between participants is to be expected unless steps are taken to abstract their respective interfaces.
The way I see it, a good rule of thumb is: When building a new class, always first consider making it a bridge.
Chain of responsibility patterns decouple a message sender from its receiver by giving many objects a chance to handle the message. The identity of the eventual service-providing object(s) may not be known to the sender, so the chain of responsibiliy routes the request until it is adequately handled.
A good place to find chain of responsibiliy patterns is within easily traversed hierarchies, like containership. With containership the logic is usually simple: "If an object cant handle a request, then delegate it to its Parent". Help systems, among others, frequently use this pattern to invoke context-sensitive help. Chain of responsibility can be implemented without containership by using object pointer registration schemes.
Heres a way to structure a chain of responsibility: When in doubt, delegate to THIS.PARENT. In the example below, nested boxes delegate their Click() event upwards until it is handled. Dont be fooled by all this code, for most of it just sets up the example. The only code you really need is in the Click() methods of the container classes. The code follows, starting on the next page.
xx=CREATE("Form1")
xx.Show()
READ EVENTS
DEFINE CLASS form1 AS form
Height = 300
Width = 448
Caption = "Form1"
FUNCTION INIT()
*-- Add six nested containers
THIS.AddObject( "FirstContainer", "ctnr")
*-- Style note: nested WITH...ENDWITH statements.
WITH THIS.FirstContainer
.AddObject( "SecondContainer", "ActiveCtnr") && TAKE NOTE!
WITH .SecondContainer
.AddObject( "ThirdContainer", "ctnr")
WITH .ThirdContainer
.AddObject( "FourthContainer", "ctnr")
WITH .FourthContainer
.AddObject( "FifthContainer", "ctnr")
WITH .FifthContainer
.AddObject( "SixthContainer", "ctnr")
ENDWITH
ENDWITH
ENDWITH
ENDWITH
ENDWITH
ENDFUNC
ENDDEFINE
Sample 4. The code above generates nested containers of class Cntr as in the illustration below.
Figure 8. Nested containers. Here the grey container handles the Click() event of all its inner members.
DEFINE CLASS ctnr AS container
Name = "ctnr"
FUNCTION INIT
*-- Container placement
THIS.Top = 20
THIS.Left = 20
THIS.Width = THIS.PARENT.Width-40
THIS.Height = THIS.PARENT.Height-40
THIS.Visible= .T.
ENDFUNC
FUNCTION Click
*-- the purpose of this method is
*-- by default to pass the click
*-- event to THIS.PARENT.
IF TYPE( "THIS.Parent")= "O"
*? Error trap this.
THIS.Parent.Click()
ENDIF
ENDFUNC
ENDDEFINE
DEFINE CLASS ActiveCtnr AS Ctnr
BackColor = RGB(192,192,192) && For your viewing pleasure
FUNCTION Click
WAIT WIND THIS.Name + " is handing the message" NOWAIT
*-- If the search must stop here, omit the following
Ctnr::Click()
ENDFUNC
ENDDEFINE
Sample 5. Class definition for Ctnr and ActiveCtnr. Note that THIS.PARENT.DoHelp() is invoked upon no joy in THIS.DoHelp().
In our example we use PARENT referencing to algorithmically reckon the next successor in the chain. There are other ways to do it, including direct referencing, where each object keeps pointers to one or more successors. Assigning objects to these pointers must be managed, of course, and care must be taken to avoid both circularities and unhandled requests.
chain of responsibility can also be built with a handler class that broadcasts the message to a sequence of objects until the message is satisfactorily handled. A variant of this uses object registration mechanisms to maintain the list and sequence of objects to be called.
You may need to pass parameters along the chain. This can be problematic if a variety of classes participate in the successor chain, especially if a handler is involved and the identity of the receiver classes isnt known for certain. In this case, its most helpful to standardize message passing using objects instead of parameters. Like always, a mature and predictable programming interface helps.
Finally, its useful to note that a chain of responsibility doesnt need to end when a message is handled by a receiver in the chain. It may well be desirable for the message to be broadcast to many objects regardless.
Voyeur patterns invasively observe and react to exposed members without the knowledge or consent of the event participants. Voyeurs decouple the viewed objects by encapsulating part, or all, of the systems behavior into a controlling object.
A good place to find a voyeur pattern is at the boundary between two incompatible, incomplete, or low-cohesive systems. Voyeur activity is usually triggered by timers, or alternatively from within regularly repeating event loops.
voyeur, like observer (a pattern discussed in Gamma et al.), is a behavioral pattern. It is similar to observer except that subjects are passive they dont actively notify their observer and they dont need to know any details about the voyeurs existence.
An good example of a voyeur is Ken Levys SuperCls utility. SuperCls is a timer-activated toolbar whose function is to provide class information services while editing class methods. It works as follows: The timer polls WONTOP() and fires the toolbar.Show() and toolbar.Hide() methods as appropriate.
Figure 9. Ken Levys SuperCls.
Heres another example of a voyeur, wherein a timer regularly polls attributes and sets properties accordingly. The key here is note that the subjects themselves have no code germane to the function of the form. In this case, the form reports the position and distance of the mouse relative to its origin. The design surface looks like Figure 8, the source is in Sample6, and an illustration of the finished product is in Figure 9.
Figure 10. The design surface for the voyeur example. Note the timer.
As it so happens, using a voyeur for this implementation is cleaner than many other ways of doing it. Alternatives, which typically involve using the various MouseOver() methods to refresh the display, is less practical because:1) we cannot easily control the refresh rate, 2) we need to code many MouseOver() events because visible objects eclipse form's view of the mouse, 3) thus most objects need to "know" about the context of this implementation or, alternatively, know a mediator to accomplish the chore, and 4) rigged as it is, all the code is in a single place the observer itself.
XX= CREATEOBJECT( "FORM1")
XX.SHOW()
READ EVENTS
DEFINE CLASS form1 AS form
Height = 223
Width = 372
DoCreate = .T.
BackColor = RGB(192,192,192)
Caption = "Voyeur"
ADD OBJECT timer1 AS timer WITH ;
Top = 13, Left = 9, Interval = 100, Name = "Timer1"
ADD OBJECT command1 AS commandbutton WITH ;
Top = 161, Left = 140, Height = 29, Width = 94, Caption = "Cancel"
ADD OBJECT RowDisplay AS VoyeurTextBox WITH Top = 40
ADD OBJECT ColDisplay AS VoyeurTextBox WITH Top = 68
ADD OBJECT DistDisplay AS VoyeurTextBox WITH Top = 96
ADD OBJECT label1 AS VoyeurLabel WITH Caption="Mouse row",Top= 43
ADD OBJECT label2 AS VoyeurLabel WITH Caption="Mouse column",Top=71
ADD OBJECT label3 AS VoyeurLabel WITH Caption="Distance to origin",Top=99
*-- Here is the voyeur part
PROCEDURE timer1.Timer
THISFORM.RowDisplay.Value = PADL(MROW(),6)
THISFORM.ColDisplay.Value = PADL(MCOL(),6)
THISFORM.DistDisplay.Value= IIF(MROW()>0 AND MCOL()>0,;
PADL((MCOL()^2+MROW()^2)^(1/2),6),"Unknown")
ENDPROC
PROCEDURE command1.Click
THISFORM.Release()
ENDPROC
ENDDEFINE
DEFINE CLASS VoyeurTextBox AS textbox
Alignment = 1
Height = 24
MousePointer = 1
ReadOnly = .T.
Width = 79
ENDDEFINE
DEFINE CLASS VoyeurLabel AS LABEL
Alignment = 1
BackColor = RGB(192,192,192)
Height = 18
Left = 103
Width = 9
ENDDEFINE
Sample 6. A voyeur an unknown and unacknowledged observer.
Figure 11. The voyeur form in action. As the mouse moves, the display values are automatically updated.
Voyeurs are great for dynamically linking two uncooperating objects. For timer-based voyeurs, expect overhead loading proportionate to their number, firing frequency, code executed and, as usual, the Timer() event will occasionally get in the way when trace is running. Note that, like all things in VFP, timers dont fire while menu popups are activated.
A mediator object, depicted in Figure 11, abstracts inter-workings of two or more objects. They are communication hubs that serve to decouple objects objects dont need to know about each other, they just need to know their mediator.
Look for mediator patterns where objects are decoupled and in situations when inter-object behavior variation is present or expected. Mediators well serve situations where complex protocols must be managed, and when centralized access points are desirable.
mediator is a behavioral pattern. Other pattern names sometimes used for mediator are sender-pass through-receiver, and observer. When we speak of mediator, its a system of at least three objects messaging in a star topology.
Figure 12. The mediator provides a common connection point, centralized (and subclassable) behavior and behavior management, all with a common interface. As the number of collegues increases, the number of total communication pathways is vastly reduced.
Following are diagrams of two mediator implementations, one from Tazmanian Traders, the other from Y. Alan Griver's Visual FoxPro Codebook. The horizontal splitter control in Tazmanian Traders, which comes from the tsGen class library, contains two shapes and two scrolling command buttons. As the handle shape is moved, or the scroll buttons are clicked, messages are sent to the splitter, which then messages selected items on the form.
Figure 13. The splitter bar control in the Taztrader sample application is a mediator between the objects it contains and the objects outside.
Figure 14. The Splitter, packaged as a control class, mediates between its insides (the slider and scroll buttons) and the outside world.
In Codebook, toolbar navigation buttons call a business object class (cBizObj) which mediates in a one-way direction between the form (of class cBizObjForm) and the data behavior object (of class cDataBehavior). This simplifies the programming interface only the cBizObj class protocols need be known and allows for the easy substitution of the data behavior implementations from the cDatabehavior class. Note that several bridge patterns are evident here separating programming interfaces from implementations.
cToolbarButton and cBizObjForm
cBizObjForm and cBizObj
cBizObj and cDataBehavior
Figure 15. Hitting the Next button in Codebook. The cBizObj class mediates between the form and the desired behavior.
The study of object oriented patterns is a very new discipline. The best and most complete works have all been published in the past 18 months. If youd like to read more about object oriented design patterns, consider the following books. This list is sorted in the order I would read them.
Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software. Reading, MA:Addison-Wesley 1995.
James O. Coplein, Douglas Schmidt. Pattern Languages of Program Design. Reading, MA:Addison-Wesley 1995.
Peter Coad. Object Models: Strategies, Patterns and Applications. Englewood Cliffs, NJ, Yourdon Press 1995.
Wolfgang Pree. Design Patterns for Object-Oriented Software Development. Reading, MA:Addison-Wesley 1994.