Session E-PATT
Advanced Design Patterns
In this session for advanced Visual FoxPro developers, Steven will discuss some important and sometimes less obvious object-oriented design patterns that regularly occur in medium to large-scale Visual FoxPro applications, and demonstrate their implementation in Visual FoxPro. This free-wheeling and fast-paced session assumes that you are familiar with some fundamental object-oriented terms and principles.
Introduction
A pattern is a recurring solution to a particular problem in a given context. A design pattern is a general abstraction of the solution across the range of problems and contexts where the pattern is appropriate. Design patterns have names, and knowing (and agreeing) on their names and meanings leads to a pattern language which is useful in conveying the abstractions among ourselves. The shared vocabulary makes patterns an excellent vehicle for conveying design experience.
When properly catalogued, design patterns define the parts, collaborations, and responsibilities of classes and instances. The nice thing is they abstract the subsystem above the level of classes, instances, and code. Patterns are all about architectures, their component structures, and all their nuances.
So patterns serve as a guide to the sensible design of software building blocks, and they help us better communicate and cope. Anyone who has used a napkin to sketch the workings of a software system knows that a picture is worth a thousand words. Over the next two days, it should be come clear that a correct word can be worth many pictures.
Some design patterns, like most of those described by Gamma and Helm's Design Patterns, describe the implementation of micro-architectures. These micro-architectural design patterns are useful, for the most part, to describe small-scale object interactions.
Other design patterns are useful for abstracting large systems of objects. These are architectural design patterns. An architectural pattern is any pattern concerned with the construction context of a whole system, rather than just some part of a system.
The distinction between micro-architectures and system architectures depends on your point of view and the scale of your system. If your system has 5 objects then the micro-architecture-style design patterns are architectural patterns because they consider the structure, relative communication, and design philosophy for the system. But more commonly, architectural design patterns are used to describe the structure of bigger systems where the number of objects is measured in hundreds or thousands.
One common architectural design pattern is called Layers. Here it is:
Design Pattern: Layers
Intent
Layers is an architectural design pattern that structures applications so they can be decomposed into groups of subtasks such that each group of subtasks is at a particular level of abstraction.
Some Examples
The "traditional" 3-tier client server model, which separates application functionality into three distinct abstractions, is an example of layered design. Much has been written about the 3-tier client-server model and I won't discuss it further, other to say that this is the result of layered design thinking.
Figure 1: A simplified view of the 3-tier client-server architecture.
In a more general sense, the OSI 7-layer networking model and the Internet Protocol Stack, both illustrated in Figure 2, are networking protocols that illustrate the use of layering in network architecture.
Figure 1: The OSI 7-layer model, largely supplanted by the more recent and popular Internet Protocol Stack.
Here is a brief table describing the layers of the OSI 7-layer model.
Layer
|
Role
|
Application
|
Provides services to user. Examples include telnet, TCP, HTTP.
|
Presentation
|
Structures information and attaches semantics.
|
Session
|
Provides dialog control and synchronization facilities.
|
Transport
|
Responsible for segmenting long messages into packets. Recovers lost packets with acknowledgments and retransmissions. Flow control. Congestion control. Breaks messages into packets and guarantees
delivery.
|
Network
|
Responsible for routing packets from source to destination host. Selects a route from sender to receiver.
|
Data Link
|
Responsible for moving packet from one node (host or packet switch) to next node. Error detection and correction. Medium access.
|
Physical
|
The physical layer, the lowest layer in the OSI stack, is responsible for moving information between two systems connected by a single physical link. The physical layer provides the abstraction of
bit transport, independent of the link technology. Specifies voltage levels, bit spacings.
|
The OSI 7-layer model is a cool example because it neatly shows the general types of services required for computers to talk to each other. The Internet protocol stack is a refinement of the OSI 7-layer model, minus the Presentation and Session layers, whose services are either not needed or abstracted into the neighboring layers.
Note that each of the layers in the OSI stack don't necessarily function on distinct hardware or memory space. For example, it's common to find the Data Link and Physical layers tightly coupled and interleaved (for performance reasons) within the same Ethernet network interface card.
Context
A large system requires decomposition. One way to decompose a system is to segment it into collaborating objects. In large systems a first-cut rough model might produce hundreds or thousands of potential objects. Additional refactoring typically leads to object groupings that provide related types of services. When these groups are properly segmented, and their interfaces consolidated, the result is a layered architecture.
Benefits
- Segmentation of high-level from low-level issues. Complex problems can be broken into smaller more manageable pieces.
- Since the specification of a layer says nothing about its implementation, the implementation details of a layer are hidden (abstracted) from other layers.
- Many upper layers can share the services of a lower layer. Thus layering allows us to reuse functionality.
- Development by teams is aided because of the logical segmentation.
- Easier exchange of parts at a later date.
Downsides
The trouble with layers of computer software is that sooner or later you loose touch with reality. Layers are abstraction boundaries, and the more they encapsulate their works the more one is unaware of the application's inner works.
Layering is a form of information hiding. A "layering violation" occurs in situations where a layer uses knowledge of the implementation details of another layer in its own operations. At the limit this leads to changes to one layer resulting in changes to every other layer, which is an expensive and error prone proposition.
Layering can lead to poor performance. To avoid this penalty, in situations where an upper layer can optimize its actions by knowing what a lower layer is doing, we can reveal information that would normally be hidden behind a layer boundary.
The layers must be engineered at the outset, before the system is built.
Forces
The following is a partial list of forces that bring about layered architectures. Note that some of these forces are present to varying degrees in all software systems.
- Late source code changes should not ripple through the system
- Interfaces should be stable.
- Parts should be exchangeable.
- Possibility of building other systems at a later date with the same low-level issues as the system currently being designed.
- Similar responsibilities should be grouped to help understandability and maintainability.
- The system will be built by a team of programmers, and work has to be subdivided along clear boundaries.
A Layered model does not imply that each layer should be in a separate address space. Efficient implementations demand that layer-crossings be fast and cheap. Examples: User Interfaces may need efficient access to field validations.
Structure
|
Class: Layer J
Responsibility: Provides services used by Layer J+1 and delegates subtasks to Layer J-1
Collaborator: Layer J-1
|
Dynamics
Here are some typical interactions in layered architectures.
Delegation of Requests
Messages that percolate downwards between layers are called Requests. For example, a client issues a request to Layer J. What Layer J cannot fulfill, it delegates to Layer J-1. Note that Layer J often translates requests from Layer J+1 into several requests to Layer J-1.
Notifications
Messages that percolate upward between layers are called Notifications. A notification could start at layer J where, for example, an observer object detects an observable event. Layer J then formulates and sends a message (notification) to Layer J+1.
Caching Layers
Layers are logical places to keep information caches. Requests that normally travel down through several layers can be cached to improve performance.
Intra- and inter-Application Communications
A system's programming interface is often implemented as a layer. Thus if two applications (or inter-application elements) need to communicate, placing the interface responsibilities into dedicated layers can greatly simplify the other application's layers and, as a bonus, make them more easily reusable.
Some Layers to Consider
The GUI Layer
The principle of separating the user interface from the application proper is old. It is rarely practiced, for all the talk we devote to it. The principle of separating the user interface from the application has the hardest consequences and is hardest to follow consistently. For the most part most applications barely separate the GUI from the application code, though they claim otherwise.
Imagine the application is totally program driven, with the user interface just one driving program. I'm not talking simply of separating the interface code from the application code, I mean separate GUI and application components.
In other words: The GUI is not a part of the application. It is the first client of the application.
A typical VFP application has too much special-purpose code in the GUI. This is mostly a fundamental problem with the IDE: The easiest thing to do, by far, is to put code in those GUI control methods.
Why so much GUI code? One cause of ballooned of GUI code is that it does things that should be done by the "model" - which we'll define, for now, as simply "another layer somewhere". Another cause of GUI code bloat is many people tend to embed code to maintain various kinds of integrity among objects. The result of these things is "know it all" controls.
Another mistake many developers fall into is GUI elements that pull the data to display directly in from the domain model and then having the GUI elements update the domain objects on any changes being made. Again, nothing could be easier in VFP!
Possibly better is a system of "naïve controls" that rely on separate Renderer objects to fill them with data from the domain model objects, and to update the domain objects with the changes made by the user.
It is easier on the user if input errors are brought up directly upon entry. Having the UI outside the application separates input from error detection. First the UI will change, so isolate and make an interface to it. Then someone will remove the human from the picture entirely, with electronic interchange or another application driving the program. Therefore, just making an interface to the UI component is not sufficient, it has to be an interface that does not care about the UI.
Therefore, put the UI totally outside the application. The application proper is bounded by a program-driven interface, and the UI is just one user of that interface, perhaps not even the first. Other users of the application could be another controlling application, or a testing application. Once the GUI is separate, almost anything is possible.
Downsides
If the UI is really outside the application, what about when the user starts and then cancels a modification - where are the editing and rollback copies of the object kept? In the UI, or outside the application? What about typing errors - are they detected in the GUI or inside the application?
Answer: Keep edits in the GUI, of course! The user fumbling around in a GUI is a reality of a GUI. You want idiomatic GUI behavior and effects encapsulated in the GUI. Imagine writing a second program-driven interface, and having to deal with all these messages reminding the automated program in real-time that, say, a field is required. Yech!
Isolation Layers
Building systems requires us to bring many varied and unrelated concepts together. A typical medium or large sized system might involve diverse concepts like domain functionality, transactions, meta-data, database technology, network communication protocols, OS API calls, a GUI, etc.
Given pressure to quickly produce a reasonably fast system, it's tempting to tie these concepts closely together and so embed transaction-control code in the GUI code, or OS API code in the business code. This leads systems that are:
Hard to change: if you want to change the transaction-control system you need to scour all the GUI code to find all transaction-control related stuff
Hard to understand: business code and OS API code are, in their own right, complex and hard to understand. Mix them together and the complexity multiplies before your very eyes.
Hard to write: if you're writing business code the last thing you want to be worrying about is catching OS exceptions.
Therefore...
Write a layer of software to isolate each disparate concept or technology. These layers should isolate at the conceptual level (perhaps business code really needs to know nothing about the OS API - this is all handled transparently by some object management code) and/or at the technical level (handling unhandled exceptions raised by the object management code so they don't find their way into the business code). Isolation should be two-way (the business code 'knows' nothing of the OS API code and vice-versa).
This leads systems that are:
Easier to change: by isolating the database from the communication code we can change one or the other with minimum impact
Easier to understand: each 'bit' of the system deals with only one concept: business, networks, database
Easier to write: business people can write business code that isn't polluted with code to display dialogue boxes or handle network exceptions.
Of course the Isolation Layer itself may be complex and, possibly, represents a single point of failure. Over-application of the pattern leads to a system where everything is strongly decoupled and so the effects of system events may be unpredicatable and design or change is always 'selfish': distribution is hidden from the business designer and so they design without any thought for distribution - something which could bring the system to its knees.
In closing…
Discussion: The choice a layered architecture can have many beneficial effects on your application if it is applied in the proper way. First, since the architecture is so simple, it is easy to explain to team members and so demonstrate where each object's role fits into the "big picture". If a designer is very strict about clearly defining where objects fit within the layers, and the interfaces between the layers, then the potential for reuse of many objects in the system can be greatly increased.
A common problem with many object designs is that they are too tightly constrained to the limits of the particular application being built. Many designers tend to put too much of the logic of an application in the GUI layer. In this case, there are few, if any, domain objects that are potentially available for reuse in other applications.
Another benefit of this layering is that it makes it easy to divide work along layer boundaries. It is easy to assign different teams or individuals to the work of coding the layers in a four-layer architectures, since the interfaces are identified and understood well in advance of coding. Finally, a four-layer architecture makes it possible to code the bulk of your system (in the domain model and application model layers) to be independent of the choice of persistence mechanism and windowing system.
Layers can make for great abstractions. But remember: abstractions are illusions! Beautiful, clean, elegant abstractions are still illusions. Don't be afraid to "cheat" - for example when you need better performance - and poke through a layer's formal boundaries. This is the essence of programming. Once you have a layered architecture don't be afraid to hack, just be elegant and rigorous about how you surface your hacks to your consumers.
Pattern: Strategy
The Strategy design pattern permits attaching an appropriate behavior or algorithm at run-time. It helps keep behavior flexible by decoupling a process from the one or more potential behaviors (or algorithms) that implement it.
Intent
Some processes require variety in behavior. To achieve this variety with a minimum of fuss, it helps to package the behaviors in a separate behavior class -- thus making them interchangeable -- and thereafter tinker only with the behavior classes, independently of the process that invokes them.
Motivation
Sometimes the only difference between two classes is their behavior. It's not what the objects are that's different, but rather what they do. Subclassing a process for each new behavior is an obvious and workable mechanism. Another way, possibly better, is to identify and separate the variant parts of a process from its invariant parts, and then tinker only with the variants as required.
For example, imagine your accounting software produces invoices for companies operating in many different tax jurisdictions. The way taxes are calculated varies greatly depending on where sellers and customers are located. Additionally, taxes can vary according to what is being invoiced. Children's clothes, books, liquor, tobacco, and heating oil are examples of products that get special (and different) tax treatment almost everywhere.
Similarly, in some industries, discounts are popular sales incentives. Sales managers are constantly inventing creative new discount schemes. Sometimes discounts are constrained by the accounting software's ability to handle them.
Do you really want to deal with the potential for a separate industry or client-specific invoicing subclass for each country, state, or sales territory where your application is used? Of course not. You need a rational way to build your invoicing module so new features can be applied without polluting the underlying foundation which is otherwise largely invariant.
Setting up a Strategy Pattern
A Strategy pattern decouples a process from the algorithms that implement it. The key to setting up a successful Strategy pattern is interface consistency: give all your strategies a common interface, and the invocation process won't care which class provides the services. The best way to give strategies a common interface is, of course, to make them all belong to the same class.
Figure 1. Separating a process from its variable behaviors (Strategies) is easier if the interface defining the behaviors is consistent, and the best way to achieve this is by consolidating the strategies into the same class.
In the diagram in , the Process "owns" a separate and independent Strategy instance that has an interface defined in the AbstractStrategy class. This ownership means, in practice, that the strategy objects are destroyed when the Process is destroyed. A common variant is for the Process to own more than one Strategy instance, where the Strategy objects are instanced as needed and thereafter retained in case they are needed again. This "pay-as-you-go" behavior means you only pay the overhead for the objects you actually need.
Applicability
Here are some generally applicable reasons to consider a Strategy pattern:
- You need (or expect you'll need) the ability to configure an object with more than one algorithm or behavior.
- You'd like to simplify a class design by encapsulating and extracting its variable elements.
- You want to hide implementation details, either because of their proprietary nature or their complexity.
- Your class can invoke operations that can share the same programming interface.
- You need to provide platform or implementation-specific services.
- You need to vary the coordination and execution of application or framework services.
Visual FoxPro Example
Everywhere I go, customers have creative ideas about how discounts should work, or how they should be presented. (Henceforth we'll only talk about discounts, but everything here applies also to processing taxes).
Say your invoice class currently has the following interface:
DEFINE CLASS Invoice AS Document && Baseclass Custom
FUNCTION CalcPrices()
FUNCTION CalcDiscounts()
FUNCTION Print()
ENDDEFINE
Now suppose a client requires a new sort of discount, a significant variant. What to do? Here are a few options:
- Add a CASE statement to CalcDiscounts() to handle the new discount. This works of course but, at the limit, giant CASE statements don't scale or maintain very well.
- Subclass the whole invoice class and override the CalcDiscount() method. This is probably workable, but it isn't pretty if you considering that discounts is just one among many factors that can trigger thoughts of subclassing an invoicing class.
- Create a separate class whose function is solely to calculate discounts, and attach an instance of this class (or one of its subclasses) as required. This scenario is what this article is all about.
Here are three simple discount strategy classes. The first calculates a discount for each line item. The second calculates a discount based on a threshold invoice total, and the third calculates discounts for certain product types. All three are subclasses of an abstract discount type. These are just code skeletons, but from this it's easy to see that the AbstractDiscount class defines the whole class interface. It doesn't have to - you can modify the interface of subclasses to suit your mood - this is simply the way I prefer to do things.
DEFINE CLASS AbstractDiscount AS Custom
*-- Define the inerface for all discounts
Rate = 0
&& The discount rate
nThreshold= 99999999 && Minimum purchase
cType = "None" &&
Type constraint
FUNCTION Execute( toObject)
RETURN .F.
ENDDEFINE
DEFINE CLASS LineItemDiscount AS AbstractDiscount
Rate= 0.1
FUNCTION Execute( toInvoice)
*-- Apply a discount to each line item
LOCAL lni
FOR lni= 1 TO ALEN(toInvoice.LineItems,1)
toInvoice.LineItem( lni).Discount = THIS.Rate * ;
toInvoice.LineItem( lni).Price
ENDFOR
ENDDEFINE
DEFINE CLASS LineItemDiscount AS AbstractDiscount
nThreshold= 1000
FUNCTION Execute( toInvoice)
*-- Apply a discount if the total exceeds a threshold
LOCAL lni, lnTotal
lnTotal = 0
FOR lni= 1 TO ALEN(toInvoice.LineItems,1)
lnTotal=lnTotal + toInvoice.LineItem( lni).Price
ENDFOR
IF lnTotal >= THIS.lnThreshold
toInvoice.Discount= lnTotal* THIS.rate
ENDIF
ENDDEFINE
DEFINE CLASS ProductTypeDiscount AS AbstractDiscount
nRate= 0.1
nThreshold= 1000
cType= "Dog Food"
FUNCTION Execute( toInvoice)
*-- Apply a discount to each qualifying line item
LOCAL lni
FOR lni= 1 TO ALEN(toInvoice.LineItems,1)
IF toInvoice.LineItem( lni).cType= THIS.cType
toInvoice.LineItem( lni).Discount = THIS.Rate * ;
toInvoice.LineItem( lni).Price
ENDIF
ENDFOR
ENDDEFINE
Some Downsides
Classes that cooperate as in a Strategy pattern are certainly flexible, but separating algorithms into independent classes can incur significant disadvantages. The most onerous I've found occurs when the strategy class needs non-trivial access to data structures in the calling process. For example, the strategy classes may need to know alias names, or to share arrays and pointers. In this situation, I've usually found it best to use a separate object to package everything about the calling process that the strategy may require. In other words, instead of passing 36 parameters to strategy objects, pass a single object having 36 properties. This keeps the interface clean - we pass at most one parameter, an object - and makes it easier to reuse processes and strategies as generalized abstractions.
Pattern: Decorator
A decorator wrapper (I'll just call it Decorator) is an elegant OOP technique that can be handy in a wide variety of situations. In particular, the Decorator pattern is useful for extending the functionality of a class without polluting (or even needing) the original source.
Subclassing isn't always cool
But before we get to the decorator pattern, lets examine what happens when we allow a class to grow organically with inheritance. To illustrate, consider the following class interface for a frog:
DEFINE CLASS Frog AS CUSTOM
FUNCTION Jump(nValue)
FUNCTION Eat()
ENDDEFINE
Suppose we later need a singing Frog. Well, within our current simple hierarchy, we already have several obvious options.
- We could simply augment the DancingFrog with a Sing() method, perhaps renaming the class, if that's possible, to EntertainingFrog to better reflect its enhanced functionality.
- We could subclass the DancingFrog, add a Sing() method to the new subclass, and call the new class, say, DancingSingingFrog.
- We could insert a new Frog subclass in the hierarchy, before DancingFrog, endow it with a Sing() method, and call it, say, SingingDancingFrog.
- We could make all Frogs able to sing by augmenting original Frog class.
- What the heck, we could vastly simplify this rapidly exploding hierarchy by eliminating the DancingFrog class and give every Frog the Sing() and Dance() methods in a single and monolithic Frog class.
So, buffeted by the needs of this current implementation, you make your choice and you take your chances. What if you later needed a burping frog, one that belches after meals. Do we subclass again? If so, where? Do we add a Burp() method, or do we simply augment the Eat() method because problem analysis indicates that burping naturally follows from eating. Again, which hierarchical level is best for modification? And later, when you need a frog that can beg, roll-over, shake-a-leg, and stay, a new question eventually arises: "How did this Frog class become such a mess?".
At the outset, a pragmatic reuse artists would be correct in asking when, if ever, will the need for a singing, dancing, and belching frog ever arise again? Why not just augment the frog for this instance, without polluting our general, simple, and reusable Frog class?
The Decorator alternative
The answer may be to use a decorator. A decorator is, in essence, a class with mostly "pass-through" behavior. It "wraps" a class by reference, forwarding all messages to the reference except for the messages the wrapper is designed to intercept. Setting up a decorator takes a bit of work, but thereafter it's a snap to use. Consider the following DecoFrog class:
DEFINE CLASS DecoFrog AS CUSTOM
oRealFrog= .NULL.
FUNCTION INIT( oFrog)
THIS.oRealFrog= oFrog
FUNCTION Jump(n)
THIS.oRealFrog.Jump(n)
FUNCTION Eat()
THIS.oRealFrog.Eat()
ENDDEFINE
Sample 4. Notice the key element of decorators: They are "transparent", passing through all messages except for those that need to be intercepted. Creating an abstract decorator class (like the one above) that passes through everything is the first step. Now to augment the frog for a particular instance, we can use a simple subclass of the decorator without polluting the Frog class.
To the outside world, the DecoFrog class has the same programming interface as a Frog. But it's not a Frog, it's a lens through which we can "see" a Frog. If we need a specialized one-off Frog, like one that sings, we could do this:
DEFINE CLASS SingingFrog AS DecoFrog
FUNCTION Sing()
? WAIT WINDOW "It's not easy being green..."
ENDDEFINE
Similarly, we can define a dancing frog by simply subclassing the class DecoFrog as follows:
DEFINE CLASS DancingFrog AS DecoFrog
FUNCTION Dance()
DecoFrog::Jump(+1)
DecoFrog::Jump(-2)
DecoFrog::Jump(+1)
ENDDEFINE
Retrofitting a decorator is easy. Where before your code looked like this:
Kermit=CREATE( "Frog")
A singing frog can now be substituted at run-time like this:
Kermit=CREATE("Frog")
Kermit=CREATE("SingingFrog",Kermit)
or more succinctly:
Kermit=CREATE("SingingFrog",CREATE("Frog"))
(Note that nesting CREATEOBJECT statements work just fine in Visual FoxPro).
Additionally, decorators can be chained, with functionality added or modified as needed! This gives you considerable pay-as-you-go flexibility. For example, to build a singing and dancing frog, do as follows:
Kermit=CREATE("Frog")
Kermit=CREATE("SingingFrog",Kermit)
Kermit=CREATE("DancingFrog",Kermit)
or, if you prefer one-line of code:
Kermit=CREATE("DancingFrog", ;
CREATE("SingingFrog",CREATE("Frog")))
Which creates an object relationship illustrated by the following object diagram:
Figure 2. Two decorators chained together, both augmenting the object.
Well, Tada! We now have an ordinary Frog named Kermit that appears, in this instance, with the ability to sing and dance, and we didn't need to pollute the Frog class to get it. In fact, we didn't need the source to class Frog.
Decorator Benefits
Here are some benefits that come from using decorators.
- Decorators can be an effective substitute for multiple-inheritance, which isn't supported in Visual FoxPro. In our Kermit the Frog example above, in some languages we could have used multiple inheritance to combine the Frog class and, say, an Elvis class to produce an entertaining frog ("Thankyouverymuch"). What we did instead is wrap an ordinary Frog object in various entertainment objects to extend the frog's capability as needed.
- You incur resource expenses on a pay-as-you-go basis because you can decide at run-time what types of objects to create. Also, you don't need to foresee all the future functionality of a class, deferring to decorators as new needs arise.
- You can extend the functionality of a class knowing only its interface; source is usually not required.
- If you use a decorator to endow your classes with one-off characteristics, you can neatly avoid extending your class hierarchy to support the characteristic.
- In some situations you may be able to use a lightweight decorator -- one that doesn't expose the whole interface of the wrapped class - in order to accomplish a simple task.
Decorator Downsides
As with all things, the "no free lunch" pattern applies: You cannot get the added flexibility afforded by using a decorator without some tradeoffs. Here are some of them:
- A decorated object is not the same thing as the object itself. In our Frog example, if the line THIS.Eat() appears somewhere within the Frog class, that will call the Frog's Eat() method, and not the
Eat() method that may be enhanced by a decorator.
- Decorators are best used with classes that use access functions to expose their properties. Querying and setting decorator properties without pass-through access functions does not touch the object being decorated.
- It takes more time to instantiate an object if one also instantiates one or more decorators, and once created, it takes slightly more time for messages to filter through layers of decorators to reach the object itself.
- People who aren't familiar with decorators are likely to find the proliferation of wrapper objects confusing.
- A decorator is, in fact, a second distinct class whose interface must be maintained in concert with the class it decorates. This can be problematic, but is alleviated somewhat by making the decorator a subclass of an abstract class, as in the diagram below. In this way, Decorators automatically get new interfaces, allow you to wire the "pass-through" behavior at a later time. In the class diagrams below, if the abstract class defines the complete interface for the concrete class (as it should), then new Decorator appear automatically.
Figure 3. One of the difficulties of managing a Decorator is maintaining its interface in synch with the objects to be decorated. Problems can be greatly alleviated by making the decorator a subclass of an available abstract class, one whose sole purpose is (by definition) to define the class interface.
Pattern: Proxy
Provide a surrogate or placeholder for another object to control access to it. Proxies are usually decorators in everything but name.
Problem
How to provide correct and/or efficient access to an object that may be resource intensive or otherwise require protection?
Context
A client needs the services of an object, but direct access is technically possible but questionable.
Forces
- Some objects are expensive and cannot be instanced at the outset or, once instanced, perpetually held in memory.
- Some objects require security access, while others may be subject to hostile attack.
- Some objects may have complex protocols and efficient programming requires a more simplified interface.
Solution
Make a component's clients communicate with a representative rather than with the component itself.
Resulting Context
Three actors: The client (as usual), the original object (as usual), and an intervening object (the Proxy) which serves as a placeholder for the original object and forwards requests as appropriate.
Uses for the Proxy pattern
There are many uses for the Proxy pattern.
- A Remote Proxy encapsulates and maintains the physical location of the original object.
- A Protection Proxy protects the original from unauthorized access.
- A Cache Proxy uses a data area to temporarily hold results, and has strategies to maintain and refresh the cache.
- A Synchronization Proxy controls multiple simultaneous client accesses.
- A Counting Proxy is useful to add reference counting mechanism to classes so that unused objects can be destroyed in a timely fashion.
- A Virtual Proxy loads missing parts of the original on demand, thus delaying the consumption of resources until they are needed.
- A Firewall Proxy protects the original from potentially hostile access.
Of course, you could combine two or more of the above roles.
Pattern: Template Method
A template method is an abstract algorithm that references other methods. Template methods define the specific steps in an algorithm, often without much regard for implementation details. For example, the following method, used in military software to land jets on aircraft carriers on foggy nights, is an algorithm that directly calls four other methods:
PROCEDURE Carrier_Landing
THIS.Locate_Carrier()
THIS.Line_Up()
THIS.Final_Approach()
THIS.Hit_The_Deck()
ENDPROC
This is exciting stuff. Forgive me, please, for not testing this.
The methods invoked by template methods are called hooks. Hook methods are places where adaptation can occur by subclassing. It's the job of hooks (and especially hook subclasses) to define, augment, or override each step in the algorithm. Hook methods are not to be confused with hook operations, which are defined in the next pattern. Hook operations are places, back-doors if you will, where external programs may be explicitly called to augment or override an existing method without subclassing.
The Template Method pattern, which is the interaction between template methods and hooks, is found in, among other places, all patterns that deal with abstract classes and abstract methods.
Intent
Codify the structure of an algorithm in a method, but purposefully leave the implementation steps to other methods, thereby making the algorithm partially abstract (or as abstract as it should be).
Motivation
As a general rule, the steps in most software processes are known, as is their sequencing. For example, we authenticate users before giving them access to sensitive data; our programs clean-up before termination; we locate the runway before we begin our final approach; and so on.
In higher level abstractions, however, that's often all we know. We know that our programs should clean-up before termination, but exactly what this cleanup means - and exactly how it will be done - isn't of immediate high-level concern. More importantly, how the program cleans-up probably should be flexible so it can be done correctly under different circumstances.
A class's ability to adequately adapt is a virtue. Template methods are designed with exactly this in mind.
Structure
A good way to think of the Template Method pattern is template methods correspond to a "frozen" (invariable) part of a framework. The template methods call "hot spots" (hooks), which are domains that are purposefully adaptable.
Figure 4 illustrates a this. The frozen part of an algorithm is distinct from its hot spots, which are meant to be adapted.
Figure 4. This diagram illustrates a central theme of the Template Method pattern, namely that variable elements are kept distinct in the interest of easier subclassibility.
Visual FoxPro Example
Template Method patterns are among the easiest to understand and use, and they can be found everywhere. Anyone with top-down design experience will quickly recognize an instance of the Template Method design pattern.
Consider the following task: You must write a Visual FoxPro behavior class to direct a robot to search for contraband and explosives. It's execute method might look like this.
DEFINE CLASS RobotBehavior AS CUSTOM
FUNCTION Execute() && This is our Template method
THIS.Ingress()
THIS.Search()
THIS.Egress()
THIS.GoSomewhereSafe()
THIS.SetWaitForInstructions()
ENDFUNC
FUNCTION Ingress()
*-- Subclass and define your own behavior.
*-- Note that it isn’t unusual for template
*-- methods to call other template methods.
*-- Example, THIS.Ingress() could be another
*-- template method, like this:
*
* THIS.ApproachRamp()
* THIS.NavigateRamp()
* THIS.EnterAirPlane()
*
ENDFUNC
FUNCTION Search()
ENDFUNC
FUNCTION Egress()
ENDFUNC
FUNCTION GoSomewhereSafe()
ENDFUNC
FUNCTION SetWaitForInstructions()
ENDFUNC
ENDDEFINE
Listing 1 , illustrating a template method called ::Execute(), which calls five hooks. You would subclass this class to provide different implementation behavior.
I'm constantly amazed at how easy it is to write complex real-time robot control systems in Visual FoxPro.
In the example above, the template method is the Execute() method. In and of itself, the method (and hence the class) is completely useless - in spite of its ambitious intent, it does nothing. What it provides, however, is an abstract basis for the development of specialized subclasses which can make robots to search cars, buses, airplanes, embassies, even your own home.
What to Expect From Template Method Patterns
On balance, classes built with template methods are easily adapted by subclassing because the major steps of an algorithm and their interfaces are pre-defined. All we need to do is subclass, and provide our own behavior where appropriate.
If you use VFP version 5.0, you have a fair degree of control over what downstream users can and cannot do. For example, by using the new Hidden methods and properties features, you can limit what the user's classes can see and accomplish with subclasses, or force them to use access methods to access class services to make the class more robust.
The only real downside is you must which methods have default behavior that may be overridden, and which ones have no default behavior, meaning they must be overridden.
Pattern: Hook Operations
Author's note: I would like to emphasize the contribution of Wolfgang Pree's work in the foundation of many ideas discussed in this pattern (see References below).
Hook operations are places within procedural or method code where adaptation is specifically designed to take place. The Hook Operations design pattern provides a consistent interface or methodology for adapting software using hooks. Hook operations usually do nothing by default; their invocation is the exception, not the rule. Correctly implemented, hook operations allow almost limitless extensibility.
Intent
Explicitly provide places where your software can be extended using a consistent extension mechanism, document the interface, and permit downstream developers to customize to suit their needs.
Motivation
Object oriented toolkits and frameworks are composed of related and reusable classes that give general-purpose functionality. Toolkits provide code reuse, while frameworks provide architectural reuse. In either case, the goal is to be as flexible and extensible as possible.
There are several ways that toolkits and frameworks can be adapted, the most obvious of which is to subclass and then augment, or override, the original behavior. Subclassing pre-supposes several things that usually cannot be guaranteed: Firstly, to subclass, source code is required. Secondly, subclassing requires specialized knowledge of the classes and understanding of their functional interactions; certainly some subclassing tasks are trivial, but many are not. Third, it's extremely difficult to know every process that creates the parent class of this new subclass, and you may need to doctor these processes appropriately or some instances won't behave as you expect.
Hook operations are an alternative to all this. Hook operations are well defined back-doors where folks can attach programs to modify behavior, or even replace the process that is being hooked.
As a quick aside, we need look no further than Visual FoxPro's own architecture for a fine example of hook behavior. All of FoxPro's programmable events, for example, fire before the actual native VFP behavior. The code you put in, say, a control's Click() event, it executes before the actual Windows click message, and using NODEFAULT you can instruct VFP not to fire its native behavior. So when you write event code, you are in fact hooking an instance of VFP's base classes and augmenting or overriding the native behavior.
Another example is the Visual FoxPro Class Browser, which is a real hookfest. By using its AddIn() method, and by specifying a method to hook, you can permanently register custom behavior for a variety of events. Registering the add-in is persistent because the
AddIn() method creates a record in BROWSER.DBF, which lives in your HOME() directory.
For example, with the registration instructions and the code below, you can hook the Browser's ExportClass() method to change the content of the exported class code to make it hog less space. The resulting code contains spaces instead of tabs (which I personally prefer), and also eliminates all the blank lines that are otherwise generated. This is very handy if you need to cut and paste code for an electronic message
This particular add-in places the modified class code in the Windows clipboard. As far as I know, there is no way to hook the ExportClass() method just before it fills the class code display window. We'll call this add-in on the
CmdExport.Click() hook, fill the clipboard, modify the clipboard contents, and then return to the regular export process. Our modified code in in the clipboard, not in the Class Browser's code window.
*-- NoHog.PRG
*-- Browser Addin to capture the class browser's
*-- exported class code and make it hog less space.
*--
*-- Note: The modified code is placed on the
*-- clipboard; the code in Browser’s code
*-- display window is unchanged.
*--
*-- To register, invoke BROWSER, then in the command
*-- window:
*-- _oBrowser.Addin( "NoHog", "cmdExport.Click", ;
*-- "NoHog.PRG")
*-- Browser Add-Ins pass a reference to Browser
LPARAMETER oBrowser
*-- Put the class definition on the clipboard
*-- Note: The .F. parameter on ExportClass() supresses
*-- the class code display window. Here we just want
*-- to place the code on the clipboard. The code display
*-- window will appear naturally as part of the
*-- downstream behavior which executes after this add-in.
_CLIPTEXT= oBrowser.ExportClass(.F.)
*-- Tabs, begone! I prefer two spaces instead.
_CLIPTEXT=STRTRAN(_CLIPTEXT, chr(9), SPACE(2))
*-- Excess blank lines, begone!
DO AT( CHR(13)+CHR(13), _CLIPTEXT) > 0
_CLIPTEXT=STRTRAN(_CLIPTEXT, CHR(13)+CHR(13), CHR(13))
ENDDO
*-- Returning .F. tells BROWSER to execute its regular
*-- cmdExport.Click() method as usual (which displays
*-- the standard results in a class code display
*-- window, wasteful of white space as always.
*-- Remember that your modified stuff is in the
*-- clipboard.
RETURN .F.
Listing 2: A Class Browser hook to send reformatted source code to the clipbpard.
Visual FoxPro Example
Here is a neat way to implement hook operations: Define a custom property to hold reference to a hook object, and thereafter always call this hook object before you execute your standard behavior.
To implement a Hook Operation pattern you need 1) to code the hook operation in your base class methods, and 2) design and maintain hook classes.
Here's the stripped-out core of a hook operation, without much regard for anything but the essentials. In real life, you might use PEMSTATUS() to check the existence of the hook method, as well as check the TYPE() of the hook itself. I've done this elsewhere, but not here, the interest is clarity.
DEFINE CLASS SomeSubclass AS SomeClass
*-- This is the hook reference
oHook= .NULL.
FUNCTION SomeMethod( txPassed)
*-- Here is a raw hook call
IF ! ISNULL( THIS.oHook)
THIS.oHook.SomeMethod( txPassed)
ENDIF
* << YOUR CODE HERE >>
ENDDEFINE
Listing 3 , a simple hook operation, called early in ::SomeMethod()
Here are some facts about the example above:
- We've provided a back-door to execute SomeMethod() in any object attached to the oHook member.
- Our code executes after the hook call. This is a judgement call, and you may wish to have your code execute before the hook operation.
- The method, as coded, provides no mechanism to override the code that follows the hook. Here's one way to do this, using a slight modification:
DEFINE CLASS SomeSubclass AS SomeClass
*-- This is the hook reference
oHook= .NULL.
FUNCTION SomeMethod( txPassed)
*-- Here is a hook call that can override our code
IF ! ISNULL( THIS.oHook)
LOCAL lxTest
lxTest= THIS.oHook.SomeMethod( txPassed)
IF ! lxTest
RETURN
ENDIF
ENDIF
* << YOUR CODE HERE >>
ENDDEFINE
Listing 4 , a simple hook operation, called early in ::SomeMethod(), that overrides the remainder of ::SomeMethod() with a return value.
Now that we can override our default behavior by agreeing that an "override" hook returns logical false. This is not the best solution, obviously, because the convention that "override hooks return false" really isn't sustainable. All sorts of reasons exist for a method to return logical false; using this protocol to manage the behavior of all your hooks, forever, is odious.
A solution I prefer is a sneaky use of a persistent and unused legacy FoxPro system memory variable to set a flag, as follows:
DEFINE CLASS SomeSubclass AS SomeClass
*-- This is the hook reference
oHook= .NULL.
FUNCTION SomeMethod( txPassed)
*-- Here is a hook call that can override our code
IF ! ISNULL( THIS.oHook)
THIS.oHook.SomeMethod( txPassed)
*-- In this example, hooks use the unused but
*-- available _BOX system memory variable to
*-- communicate an intended NODEFAULT.
*-- So in hook code, _BOX=.F. means NODEFAULT
IF ! _BOX
*-- Reset _BOX for the benefit of the next hook
*-- that may need it.
_BOX= .T.
*-- "NODEFAULT", so we’re done.
RETURN
ENDIF
ENDIF
* << YOUR CODE HERE >>
ENDFUNC
ENDDEFINE
Listing 5, a simple hook operation, called early in ::SomeMethod(), that overrides the remainder of ::SomeMethod() by setting a handy, public, and otherwise unused flag.
The help file says that _BOX system memory variable is "Included for backward compatibility. Use the Report Designer instead". Well, I say: use it for hooks to communicate to their calling methods instead. Voila. Now it doesn't matter what hook methods return; if they set _BOX to .F., that means, in effect, NODEFAULT.
Here is a more illustrative example of the benefits of hooks. Here we'll hook the RightClick() method of a textbox to provide a context sensitive popup menu. In practice you could hook all the common methods of all your base classes to do a variety of things.
The class definition, complete with its seeded RightClick() method, looks like this:
***************************************************
*-- Class: appTextbox
*-- BaseClass: Textbox
DEFINE CLASS appTextbox AS Textbox
*-- Reference to a hook object
ohook = .NULL.
PROCEDURE RightClick
*-- Delegate to any hook
IF ! ISNULL( THIS.oHook) AND ;
TYPE( "THIS.oHook") = "O" AND ;
PEMSTATUS( THIS.oHook, "RightClick", 5)
_BOX= .T.
THIS.oHook.Rightclick()
IF ! _BOX
RETURN
ENDIF
ENDIF
ENDPROC
FUNCTION Release
*-- It’s always wise to clean up your pointers
THIS.oHook= .NULL.
RELEASE THIS
ENDDEFINE
Listing 6, a textbox with a RightClick hook.
Here's a RightClick()-seeded hook object that works extremely well with textboxes and edit boxes to bring up a handy edit menu:
DEFINE CLASS lblmousehook AS label
Caption= "MouseHook"
Name = "mousehook"
PROCEDURE RightClick
*-- Bring up a handy edit tools menu
DEFINE POPUP shortcut SHORTCUT RELATIVE FROM MROW(),MCOL()
*-- Cut
DEFINE BAR _med_cut OF shortcut PROMPT "Cu\<t" ;
KEY CTRL+X, "Ctrl+X" ;
MESSAGE "Removes the selection and places it onto the Clipboard"
*-- Copy
DEFINE BAR _med_copy OF shortcut PROMPT "\<Copy" ;
KEY CTRL+C, "Ctrl+C" ;
MESSAGE "Copies the selection onto the Clipboard"
*-- Paste
DEFINE BAR _med_paste OF shortcut PROMPT "\<Paste" ;
KEY CTRL+V, "Ctrl+V" ;
MESSAGE "Pastes the contents of the Clipboard"
*-- Select All
DEFINE BAR _med_slcta OF shortcut PROMPT "Se\<lect All" ;
KEY CTRL+A, "Ctrl+A" ;
MESSAGE "Selects all text or items in the current window"
*-- Clear
DEFINE BAR _med_clear OF shortcut PROMPT "Cle\<ar" ;
MESSAGE "Removes the selection and does not place it onto the Clipboard"
ACTIVATE POPUP Shortcut
ENDDEFINE
Listing 7, a usable RightClick() hook usable with code in Listing 6.
To set up this basic hook do the following:
oForm= CREATE("MyForm")
oForm.AddObject( "oTxtbox", "appTextBox")
oForm.AddObject( "oMouseHook","lblMouseHook")
oForm.otxtbox.Visible= .T.
oForm.otxtbox.oHook= oForm.oMouseHook
oForm.Caption= "RightClick this textbox"
oForm.Show()
READ EVENTS
When you rightclick over the textbox, control is passed to the hook, which invokes a context sensitive shortcut menu. Two things to draw from this example:
- The hooked and the hook don't need to be from the same base class. Hooks can be lightweight objects, like labels or lines.
- Hooks can be shared by more than one object. There is nothing stopping us from having all the text and edit boxes on our form pointing to the same hook.
Applicability
Two conflicting forces determine where and when hook operations should be used. On one hand, hook operations are most valuable at critical junctures in a process. On the other hand, if outsiders are those applying hooks, usability dictates that they should be positioned consistently at predictable junctures.
Unfortunately, it's difficult to identify and document exactly where a process' critical junctures lie - even assuming we could predict the needs of those who will apply hooks. For this reason, hook operations are usually found, predictably, either at the beginning or at the end of methods.
It's not possible to have hook operations at both the beginning and the end of methods because of inheritance. When one executes a ParentClass::Method() call, executing both "pre" and "post" hooks at that single point isn't likely desirable.
How, then, to provide hooks both at the beginning and at the end of a particular process? It's easy if you use a multi-part process. For example, consider a commandbutton whose a Click() calls a particular method, as follows
DEFINE CLASS HooklessProcess AS CommandButton
FUNCTION CLICK
DO THISFORM.SomeMethod
ENDDEFINE
DEFINE CLASS MyForm AS FORM
ADD OBJECT oCmd AS HooklessProcess
FUNCTION Somemethod
WAIT WINDOW "No hooks here"
ENDDEFINE
Listing 8, a button without hooks.
Compare the above to this, where the "pre" hook is launched by the commandbutton, and the "post" hook is launched at the end of the method invoked.
DEFINE CLASS FullyHookedProcess AS CommandButton
FUNCTION CLICK
IF ! ISNULL( THIS.oHook)
THIS.oHook.Click && The "Pre" hook
ENDIF
THISFORM.SomeMethod()
ENDDEFINE
DEFINE CLASS MyForm AS FORM
ADD OBJECT oCmd AS StartHookLessProcess
FUNCTION Somemethod
* << Your code here >>
IF ! ISNULL( THIS.oHook)
THIS.oHook.SomeMethod() && The "Post" hook
ENDIF
ENDFUNC
ENDDEFINE
Listing 9, a button with a hook before calling a method, which contains a hook at its conclusion.
You can easily abstract this with the following rule of thumb: Events (like click and dblclick) always fire "pre" hooks, and Methods (things invoked by events) always fire "post" hooks. So you have, in effect, an invokable back-door on the way in, and another invokable back-door on the way out.
What to expect from Hook Operation patterns
Classes created with embedded hook operations are generally much more flexible than those that aren't. If you define hook operations consistently and predictably throughout your application, then it's usually possible to attach new behavior to satisfy the needs of a particular instance without subclassing. Moreover, hook operations can be reckoned and bound at run time.
Combining Templates and Hooks
Now let's take these two patterns, combine them in a variety of ways, and see what we can come up with.
First, a quick summary:
- Template methods, by definition, call at least one other method. The methods called by template methods are either other template methods or hook methods. Template methods are never the "end of the line", so to speak, because hooks methods are required to implement them.
- A regular method is conveniently defined here as a method that calls no other method. A hook method could be a regular method.
- Hook methods implement template methods. Hook methods can themselves be template methods (meaning they call other hooks), or regular methods.
- Hook operations are places where standard forms of adaptability can take place by invoking external processes. The presence of a hook operation in a method makes that method a template method (if the hook operation is actually invoked).
Here are two new definitions that we'll use from here onwards
- A template class is a class containing at least one template method. In the following diagrams we'll represent template classes like this:

- A hook class is a class that contains at least one hook method. In the following diagrams we'll represent hook classes like this:

Let's look at some ways that these classes can be deployed.
Scenario 1: Template and Hook classes are one and the same

This configuration comes up all the time. It happens when hook methods are implemented by the template class itself. Obviously, in this case, none of the classes called by the template method can be abstract. This is simply the common case when the template class is fully satisfied within its object.
A self-sufficient template class can be subclassed for new hook behavior, which leads to the following common configuration. Everyone who's programmed in an object oriented way has done this:
In other words, a self-sufficient class is subclassed and one or more hook methods is augmented or overridden to achieve a new behavior. This is what everyone immediately thinks of when they hear a phrase containing the words "benefits of OOP".
Scenario 2: Template and Hook Classes are Separate

In this configuration, there is no inheritance relationship between the template classes and the hooks that implement them. Template T and hook H are coupled in the sense that T must know the interface of H. A change in H, or a requirement change in T, implies a change in the other in order to preserve correctness.
Managing the interfaces between the two classes is not trivial in the case where T is a template due to the presence of one or perhaps many hook operations. A logical way to manage hook operation interfaces is to use the same method name in the template method and the hook operation, as follows:
PROCEDURE RightClick
*-- Delegate to any hook – assumes hook possesses
*-- a method of the same name
IF ! ISNULL( THIS.oHook)
THIS.oHook.RightClick() && Same name as the
&& method of origin
ENDIF
ENDPROC
Listing 10, naming hook methods after its invoker makes abundant sense.
Which begs the question: If template classes and hook classes share the same interface, as in the example above, why would they come from separate classes?
In Visual FoxPro, all the base classes have some methods in common. For example, you can click, right click, and double click virtually all the visual classes, so it appears the separate-classes question has little relevance. It's when you start propagating custom methods, however, that the question becomes increasingly appropriate as the dual-interface maintenance burden increases.
In summary, there may be merit to the idea that template and hook classes should belong to the same class hierarchy in order to make maintaining the interfaces a little easier, if not automatic.
On the other hand, if your template methods and hook operations don't share the same interface nomenclature, then you have the makings of a perpetual maintenance nightmare, and there is little you can do at an architectural level to alleviate that. The best you can do is use a naming convention and hope you don't forget to correctly map the interfaces together.
Scenario 3: Hooks inherit from Templates

At first glance, having hook classes inherit from template classes seems to make sense. If we define a new non-HIDDEN method in template T, then hook class H will possess it automatically. We'll still need to add hook implementation code, of course, but in the interim at least the class interfaces are automatically in sync.
Unfortunately, from a purist OOP design perspective, this is completely bogus. Here's why:
- A H fails the "is a" T test. H could be a T, but not necessarily.
- The hook class interface should ideally be a subset of the template class interface. We only need the hook methods required by the hook operations in T which, in my experience, is never the full set of methods in T.
- In Visual FoxPro it's impossible to subclass T for the purpose of overriding or augmenting a template method algorithm without requiring a whole new definition of H. Here multiple inheritance could help. The diagram below illustrates the multiple H problem. This just doesn't work from a practical perspective.
-

- Hook class methods are, in the vast majority, "no-ops". If a template object possesses a hook object for the purpose of defining, say rightclick behavior, then the rest of the hook's methods should do nothing! Imagine T::Click() calling H::Click(), which executes all the class click code (because it's inherited from T), and then H must override the remainder of T::Click() so we don't get a second execution.
- A "solution" to the above could be to override all the methods in H inherited from T except those we are actually hooking. As repugnant as this sounds, this would work. But this tells us something: our class hierarchy is upside down!
Scenario 4: Templates inherit from Hooks

This configuration is not intuitive and, at first glance, seems outlandish. Why would a template class inherit from a hook class? After all, T defines the basic algorithms, and usually you need to subclass T to implement its abstract hook methods, or to augment or override its concrete hook methods. This hardly seems worth talking about.
If we look at this configuration more closely, we can come to the following surprising (I think potentially explosive) conclusions.
Defining the hook methods in H, and making T a subclass of H, means
- … that T possesses the proper interfaces for calling hook operations. This solves the dual-interface maintenance problem.
- … that H can be as lightweight as possible, which is something we would expect from hooks.
- … that we can encapsulate all the hook management code in the hook class itself.
- … we can easily manage the proliferation of both templates and hooks in the same class, as the diagram below illustrates.
- … which means that template classes can also be hooks. This means that we can easily build chains of hooks. For example, we could have the following hook chain attached to a commandbutton:
-

Which achieves for us a major goal, namely the separation of an interface (the button) from its implementation (the behaviors). The code below creates a form with three textboxes and assigns a different behavior pattern to the RightClick() method of each checkbox.
We'll talk more about hook operations during the session.
x=CREATE("MyForm")
X.Show()
READ EVENTS
DEFINE CLASS AbstractHook AS Relation && Relations are Ultra Light!
**********************************************
* A chainable hook class
**********************************************
*-- This property, along with the SetHook method,
*-- allows us to chain hooks!
oHook= .NULL.
*-- Set the oHook reference
PROCEDURE setohook
LPARAMETERS txPassed
LOCAL llRetVal
DO CASE
CASE ISNULL( txPassed)
THIS.oHook= .NULL.
llRetVal= .T.
CASE TYPE( "txPassed")= "O"
IF TYPE( "THIS.oHook")= "O" AND ;
PEMSTATUS(THIS.oHook, "SetoHook", 5)
THIS.oHook.SetoHook( txPassed)
ELSE
THIS.oHook= txPassed
ENDIF
llRetVal= .T.
ENDCASE
RETURN llRetVal
ENDPROC
**********************************************
*-- Here we define just a two methods to hook.
*-- In practixe you'll want to hook many more
**********************************************
PROCEDURE GotFocus
*-- Delegate to the implementation chain
IF ! ISNULL( THIS.oHook) AND ;
TYPE( "THIS.oHook") = "O" AND ;
PEMSTATUS( THIS.oHook, "GotFocus", 5)
RETURN THIS.oHook.GotFocus()
ENDIF
PROCEDURE RightClick
*-- Delegate to the implementation chain
IF ! ISNULL( THIS.oHook) AND ;
TYPE( "THIS.oHook") = "O" AND ;
PEMSTATUS( THIS.oHook, "RightClick", 5)
RETURN THIS.oHook.Rightclick()
ENDIF
ENDDEFINE
DEFINE Class MyForm AS Form
AutoCenter= .T.
Caption= "Chained Hooks Example - 3 different behaviors"
ShowTips= .T.
ADD OBJECT txt1 AS MyTextBox WITH Left=9, Top=11
ADD OBJECT txt2 AS MyTextBox WITH Left=9, Top=41
ADD OBJECT txt3 AS MyTextBox WITH Left=9, Top=71
PROCEDURE Init
Local loHandle
*********************************
* First textbox gets help and edit features
*********************************
loHandle= CREATE("HelpRightClick")
THIS.txt1.SetoHook( loHandle)
loHandle= CREATE("EditRightClick")
THIS.txt1.SetoHook( loHandle)
*********************************
* Second textbox gets just edit features
*********************************
loHandle= CREATE("EditRightClick")
THIS.txt2.SetoHook( loHandle)
*********************************
* Third textbox gets random color and help features
*********************************
loHandle= CREATE("ColorRightClick")
THIS.txt3.SetoHook( loHandle)
loHandle= CREATE("HelpRightClick")
THIS.txt3.SetoHook( loHandle)
PROCEDURE Destroy
CLEAR EVENTS
ENDDEFINE
DEFINE CLASS ColorRightClick AS AbstractHook
*-- This class causes the screen backcolor
*-- to go random when the right click method fires.
nOldColor= 0
PROCEDURE INIT
THIS.nOldColor= _SCREEN.BackColor
PROCEDURE GotFocus
DODEFAULT()
IF POPUP("shortCut")
DEACTIVATE POPUP ShortCut
RELEASE POPUPS ShortCut
ENDIF
PROCEDURE RightClick
DODEFAULT()
_SCREEN.BackColor= RAND()* 255^3
PROCEDURE Destroy
_SCREEN.BackColor= THIS.nOldColor
ENDDEFINE
DEFINE CLASS HelpRightClick AS AbstractHook
PROCEDURE GotFocus
DODEFAULT()
IF POPUP("shortCut")
DEACTIVATE POPUP ShortCut
RELEASE POPUPS ShortCut
ENDIF
*-- This class adds Help to the shortcut menu
*-- when its rightclick method fires.
PROCEDURE RightClick
DODEFAULT()
IF ! POPUP("ShortCut")
DEFINE POPUP shortcut SHORTCUT RELATIVE FROM MROW(),MCOL()
ENDIF
*-- Add a separating line
IF CntBar("ShortCut")> 0
DEFINE BAR 1010 OF Shortcut PROMPT "\-" AFTER _MLAST
ENDIF
DEFINE BAR 1011 OF Shortcut PROMPT "Help" AFTER _MLAST
ON SELECTION BAR 1011 OF ShortCut Help
ACTIVATE POPUP Shortcut NOWAIT
ENDDEFINE
DEFINE CLASS EditRightClick AS AbstractHook
*-- This class adds edit features to the shortcut
*-- menu when the rightclick method fires.
PROCEDURE GotFocus
DODEFAULT()
IF POPUP("shortCut")
DEACTIVATE POPUP ShortCut
RELEASE POPUPS ShortCut
ENDIF
PROCEDURE RightClick
DODEFAULT()
IF ! POPUP("ShortCut")
DEFINE POPUP shortcut SHORTCUT RELATIVE FROM MROW(),MCOL()
ENDIF
IF CntBar("ShortCut")> 0
DEFINE BAR _med_sp300 OF shortcut PROMPT "\-"
ENDIF
DEFINE BAR _med_cut OF shortcut PROMPT "Cu\<t" ;
KEY CTRL+X, "Ctrl+X" ;
MESSAGE "Removes the selection and places it onto the Clipboard"
DEFINE BAR _med_copy OF shortcut PROMPT "\<Copy" ;
KEY CTRL+C, "Ctrl+C" ;
MESSAGE "Copies the selection onto the Clipboard"
DEFINE BAR _med_paste OF shortcut PROMPT "\<Paste" ;
KEY CTRL+V, "Ctrl+V" ;
MESSAGE "Pastes the contents of the Clipboard"
DEFINE BAR _med_slcta OF shortcut PROMPT "Se\<lect All" ;
KEY CTRL+A, "Ctrl+A" ;
MESSAGE "Selects all text or items in the current window"
DEFINE BAR _med_clear OF shortcut PROMPT "Cle\<ar" ;
MESSAGE "Removes the selection and does not place it onto the Clipboard"
ACTIVATE POPUP Shortcut NOWAIT
ENDDEFINE
DEFINE CLASS AbstractTextBox AS TextBox
*-- Set the oHook reference
PROCEDURE setohook( txPassed)
LOCAL llRetVal
DO CASE
CASE ISNULL( txPassed)
THIS.oHook= .NULL.
llRetVal= .T.
CASE TYPE( "txPassed")= "O"
IF TYPE( "THIS.oHook")= "O" AND ;
PEMSTATUS(THIS.oHook, "SetoHook", 5)
THIS.oHook.SetoHook( txPassed)
ELSE
THIS.oHook= txPassed
ENDIF
llRetVal= .T.
ENDCASE
RETURN llRetVal
ENDPROC
PROCEDURE release
*-- Release related hook objects
IF TYPE("THIS.oHook") <> "U"
THIS.oHook= .NULL.
ENDIF
PROCEDURE GotFocus
*-- We use _BOX as a flag
_BOX= .T.
*-- Delegate to the implementation chain
IF ! ISNULL( THIS.oHook) AND ;
TYPE( "THIS.oHook") = "O" AND ;
PEMSTATUS( THIS.oHook, "GotFocus", 5)
RETURN THIS.oHook.GotFocus()
ENDIF
PROCEDURE RightClick
*-- We use _BOX as a flag
_BOX= .T.
*-- Delegate to the implementation chain
IF ! ISNULL( THIS.oHook) AND ;
TYPE( "THIS.oHook") = "O" AND ;
PEMSTATUS( THIS.oHook, "RightClick", 5)
RETURN THIS.oHook.Rightclick()
ENDIF
ENDPROC
ENDDEFINE
DEFINE CLASS MyTextBox AS AbstractTextBox
*-- Reference to a hook object
ohook = .NULL.
Width=150
ToolTipText = "Right Click Me"
StatusBarText= "Right Click Me"
Value = "Right Click Me"
ENDDEFINE
Conclusion
Template methods call hook methods, and hook methods are adaptable by standard subclassing mechanisms. Hook operations are places where optional adaptation is meant to happen. Templates and hooks are related structures that can be combined in a variety of ways. One of these combinations, the case where
templates classes inherit from hook classes permits a surprising degree of configurability and maintainability.
Why?
Because if a template class inherits from a hook class, then the template class is a hook. Moreover, since template classes are (by definition) themselves hooked, then there is really no limit to the behavior that can be attached and chained to a particular template method in a template class.
Relax, this took a long time to sink into me too.
Steven has been a Fox developer since 1986. He markets Steven Black's INTL Toolkit , a multi-lingual framework for FoxPro and Visual FoxPro, which he created in 1993 and continues to refine. He has been a featured speaker at FoxPro DevCons and regional conferences, and his contributions occasionally darken the pages of VFP books and magazines. His company, SBC / UP!, is based in Kingston, Ontario, and operates worldwide. He specializes in multi-lingual, multi-site, and other challenging FoxPro projects, including out-of-control project turn-arounds and cleanups. He consults with small developers as well as Fortune 500 companies, national and international government agencies, and software development companies to elevate their development teams. He is also the creator and webmeister of the FoxForum Wiki, a popular collaborative topic-based Visual FoxPro website at http://www.stevenblack.com/wiki
|