Steven Black

Software systems conceived,developed and carried forward.

Steven Black

Hook Operations

By Steven Black

Introduction

In this article, I'll introduce a new design pattern called hook operations, which defines mechanisms to make software flexible and extendible. this is the first design pattern i've ever created from scratch, so I welcome any comments or questions you may have about it.

All software design patterns are born from the need for flexibility. But few patterns explicitly address the issue of how to make software extensible or configurable downstream or at run-time. Furthermore, the more a framework or component is reused, the more clients it serves (by definition) and the greater is the stress on it to adapt. Hook Operations are a way of adapting frameworks or components without stressing them unduly.

For the most part, the Hook Operation design pattern is applicable in both object and non-object oriented programming. I discuss both, but I only allude to the general implementation mechanisms of procedural hooks, and the code examples presented in this article focus only on object-oriented hooks. I'll cover procedural hooks in a future article.

Name

Hook Operation

Intent

The Hook Operation design pattern defines interfaces and mechanisms by which software is engineered to be flexible and extended by people other than its creators.

Motivation

Toolkits and frameworks are composed of general-purpose classes that are later adapted by others. Toolkits provide for code and component reuse, and frameworks provide for architectural reuse. In both cases, flexibility and extensibility are virtues. How to best provide this adaptability?

In Visual FoxPro, instance programming is one way toolkits and frameworks are sometimes adapted. When you work in the Screen Designer, or in non-class objects in the Class Designer, you are instance programming; that is, you are adding user-code to object instances. This is usually fine and it works, but it means the following disadvantages:

Moreover downstream subclassing constrains framework creators in future releases. The framework creators have no control of framework subclassing. This puts framework creators in a difficult position: on one hand they want to perpetually improve their classes, but on the other hand each change may break somebody's code.

Hook operations are usually a viable alternative to instance programming and subclassing.

To illustrate a simple hook, consider a control with the click method in Listing 1. The built-in click method is preceded by the hook operation THIS.PreClickHook(). The PreClickHook() method is a good place for users to place custom processing in subclasses without polluting the built-in Click behavior.

Listing 1. A simple hook. In this example the user can either instance-program or subclass and place code in the PreClickHook() method. Later we'll see other alternatives to implement hooks.

Subclassing, though, can be problematic. If we could implement hooks without subclassing, we would see the following benefits:

Source code would not be required.

Modifications would override or augment the original code, but not change it directly.

Hooks would be less likely to be affected by future changes to the underlying framework classes.

Adaptation would occur in places specifically designed for this purpose, but outside the class hierarchy, which is easier to support.

Listing 2 shows how we could modify Listing 1 to engineer hooks without subclassing. Instead of delegating to a local method, the hook calls out to an object reference which can be dynamically configured.

Listing 2. Another simple hook. In this example the hook is implemented by reference to another separate object.

Hooks usually do nothing by default; they are used by exception, if at all. A hook is a separate and distinct method or process, reserved for adaptation, in a way separates the adaptation from built-in behavior. The hook is usually invoked in such a way that the built-in behavior can be enhanced or replaced without affecting the original source and without altering the built-in behavior provided by the class.

The key thing to take from the above is hook operations are ubiquitous calls to external code and, by default and for the moment, that external code simply does nothing.

Later in this article I'll illustrate 2 sorts of hook operations. I'll just describe them for now:

Object Hooks are hooks that are fulfilled by attaching behavior objects to instances. See figure 1.

Figure 1: Object hooks illustrated, here for a single Click() method. In diagram A the instance is not hooked and functions as normal. In diagram B the instance is hooked by a hook object whose non-blank methods extend (or possibly override) the code in the instance.

Procedural Hooks are hooks that are fulfilled procedurally. This fulfillment may be a straight program call, but it could also be metadata-driven. See figure 2.

Figure 2: A metadata-based procedural hook illustrated. Here a call to a Hook Manager triggers a lookup which returns the name of a program to execute. Since hooks are used by exception, normally the lookup would return nothing.

Applicability -- where to put hooks

Hooks are appropriate in places where you want a program to be adaptable. Where might that be? Two conflicting forces determine this. On one hand, hook operations are most valuable at critical junctures in the program. On the other hand, usability suggests that hooks should be placed at predictable junctures.

In object-oriented programs, critical junctures are commonly adjacent to significant state transformations. For example, in an accounting application, the place where transactions are posted is a critical juncture. One would hope for a hook either before and after such important state transitions.

Critical junctures are difficult to identify, especially in frameworks -- criticality, after all, depends on the implementation. An irrelevant juncture in one application may be crucial in another. For completeness, all plausible junctures (all major state transformations) would need to be hooked (and documented). This is onerous.

Predictable junctures, on the other hand, are architecturally predictable places. For example, predictable junctures could be defined as where methods begin and end. Predictable junctures are sometimes (but not always) located close to critical junctures, and their predictability makes them very usable.

How close are predictable junctures to critical ones? This is a function code volume: The briefer the method code, the likelier predictable junctures are proximate to critical junctures.

The tradeoff between critical and predictable junctures is a developer-education issue. If implementers are not able to identify where hook operations are placed, then they wont be able to engineer the adaptations they require. The main benefit of hooking at predictable junctures is the hooks are much easier to identify and document.

I find it easiest to place hook operations at predictable junctures rather than fret about placing hooks at every critical juncture. But don't recommend hooking all predictable junctures. Every method of every object doesn't need a hook, and since the hook calls involve processing, it's best to use some common sense. Some common events I hook include Click(), DblClick(), RightClick(), DragDrop(), GotFocus() and a few others. For obvious reasons, I don't recommend hooking quasi-continuous events such as MouseMove() and Paint().

Structure

A number of hook structures are possible, depending on how the hook is engineered.

Internally fulfilled hooks: When hooked objects handle fulfill their hooks by instance programming or by subclassing, the structure illustrated in Figure 3 is typical. This classic structure is well known and is the basis for the Template Method design pattern as described by Gamma, Helm et al in their Design Patterns book.

Externally fulfilled hooks: If hook operations delegate to external hook implementations, this leads to many possible variants. Two examples: Figure 4 shows a case where the hook objects come from separate hook classes, and Figure 5 shows a case where the hook class is a superclass of the hooked class.

Figure 3: What I call "self-sufficient hooks" have a structure similar to that of the Template Method design pattern. Here you use inheritance or instance programming to modify the default behavior.

Figure 5: Hooked objects and their hooks can share the same interface, which implies that they can share the same class hierarchy. This is very handy from a maintenance perspective since the interfaces remain automatically in synch.

Figure 5 deserves explanation because it is so counterintuitive and admittedly convoluted. One would think that a hook couldn't possibly be from a Parentclass of what we wish to modify. But it works great! Here's why:

Hooked objects use the public interface of hook objects. Therefore giving both a common interface makes sense because of the polymorphic nature of the linkages. Furthermore if they are of the same class, then keeping the interfaces in synch is a cinch.

If a hooked object is-a hook, then the concrete objects can use each other as hooks. Effectively, this means they can call each other's services as hook operations. For example, all the controls on a form could use the form's RightClick() services, preserving the RightClick() context of the form whilst the cursor is on a control.

Since hooked objects can call hooks, and hook objects are hooks, this means they can be chained together. (I'll explain this later in the Implementation section).

Structure: Hook sequencing

Assuming we are free to sprinkle hook operations throughout our events and methods, how to best place them? To help answer this, here are our placement choices relative to the built-in behaviors we may wish to adapt:

Pre-process hooks are invoked before the usual procedure code. Pre-process hooks are especially handy if they can override (or pre-empt or replace) built-in behavior.

Post-process hooks are invoked after the usual procedure code. Unlike pre-process hooks, post-process hooks cannot control the execution of the procedure which invokes it.

Mid-process hooks are invoked in mid-procedure. I won't discuss this option further, but bear in mind that these can be useful, especially within monolithic procedures.

Participants

There are two participants in the hook operation pattern. Note that the pattern can be extended, so one participant may play both roles.

Hooked Object Contains a template method which contains one or more hook operations.

Hook Object Not necessarily distinct from the Hooked Object Implements the hook

Implementation

This section briefly shows some ways hook operations can be implemented in Visual FoxPro

1. A simple pre-process hook

Listing 3 shows a simple call to a pre-process hook. This example uses the common convention of naming the pre-process with the prefix "pre", and similar hook operations are found in a number of VFP frameworks.

Listing 5. Pre and post-hooks surround a standard method. If you intend this method to be subclassed, then avoid this!

However you do it, combining pre and post-hooks, as in listing 5, is not recommended if the method is to be subclassed. If you subclass the click method of listing 5, and use DODEFAULT() or the scope resolution operator (::) to invoke this parent method, then youll fire both the pre and post-hook at that point. Depending on the hooks, this can cause unexpected results because both hooks will fire as part of the scope resolution invocation.

4. A delegated pre-process hook

In a generalization of Listing 3, we could substitute THIS.oHook to reference a specialized hook object, as illustrated in Listing 6.

Listing 6. A pre-hook method belonging to a hook object. This gives us flexibility to substitute behavior objects, as well as accomplish the same thing as Listing 3 by setting THIS.oHook=THIS.

5. A simple polymorphic pre-hook

Listing 7 improves on Listings 3 and 6 because it uses a clean implementation heuristic: Hooks are called polymorphically. In other words, the Click() method of an object calls the Click() method of its hook.

Listing 9. A dramatically more flexible version of Listing 5. The pre-hook is called before the event code, the event code calls a method, and a post hook ends the method. Thus we have both pre- and post-hooks and few of the subclassing problems we'd have if both hooks were in the event code.

I personally think that code structured like that of listing 9 is very slick. Note that the event (like click() and dblclick()) fires the "pre" hook predictably near the beginning of its execution, and the method (the thing invoked by event) fires the "post" hook predictably the end of its execution. Thus we have, in effect, a hook on the way in, and another on the way out. Other advantages:

Code in the user-interface object's events is kept to a minimum, thus making it easier to reuse.

The behavior of the user-interface object can be totally controlled by the pre-process hook.

Pre and post hooks can coexist in the same process without any inherent subclassing problems.

The locations of the all such hooks -- here and elsewhere -- is easily predictable with a simple heuristic: Events fire "pre" hooks, events then fire methods, and the methods fire "post" hooks..

However I have just one problem with all this: the hooks in Listing 9 are dependent on the hook classes supporting a particular interface. One promising alternative is to vastly simplify the hook interface by having a single hook method to which we pass an appropriate context identifyer, like PROGRAM(). This leads to our last implementation topic, which is to use procedural hooks.

8. Separated pre and post-process hooks with a generic interface

Listing 10 shows a variant of Listing 9 that decouples the hook from its implementation.

Listing 10, a component with a hook before calling a Click implementation method. The Click implementation method contains its own hook at its conclusion. Note that the hook calls are made to a hook manager method which permits a clean and generic interface.

The key here is the clean and invariant call, THIS.oApp.HookMgr( PROGRAM()), which allows us to have a single hook method serve for all application hooks. Of course, considerable logic may be required within the oApp::HookMgr() method but, on the other hand, this would allow us to table-base the hook operations, which is infinitely more flexible than hard-coding hooks. This mechanism is similar to the one used internally by the VFP Class Browser. More on this in the next article.

Known uses

VFP Class Browser: The VFP Class Browser 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.

Markus Egger's PowerBrowser: The public-domain PowerBrowser tool is an example of how a fully hooked application like VFP's Class Browser can be considerably adapted.

The INTL Toolkit: The INTL Toolkit, which I wrote, uses hook objects throughout.

Visual FoxPro Example

Here is a simple example of a hook system. Here we have a form with a textbox and a combobox connected by hooks as shown in figure 6.

Some things of note in the sample code:

Hooks are chained. The combobox, for example, chains through a number of objects, first picking up Dropdown() and Interactivechange() behavior, followed by the Form's hooks, which in this case attach a 2-part Rightclick() behavior.

The Textbox's RightClick() is supplemented by the Form's RightClick() and that of the Form's hook, in this case some additional debug choices (full functionality not wired-in).

Note the GarbageCollect() procedures which are woven into the Release() processes to clean up these pointers. Without this, the form will never release without CLEAR ALL. In some versions of VFP, expect a GPF without this pointer clean-up.

Figure 6: The sample provided creates this system. The combobox hook chain starts is composed of two behavior objects before hooking to the form and its hook. The textbox hook chain is similarly extended by the form and its hook object.

When you rightclick over the textbox, control is passed to the hook, which invokes a context sensitive shortcut menu. Some other things to draw from this example:

The hooked objects and the hooks dont need to be of the same Baseclass. Hooks can be lightweight objects, like labels, lines, or even Relations (which are ultra-lightweight).

Hooks can be shared by more than one object. There is nothing stopping us from having all the controls on our form pointing to the same hook.

Conclusion

Classes with code embedded with hook operations are generally more flexible than those that arent. If you define hook operations consistently and predictably throughout your application, then its 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.