The Hooks and Anchors Design Pattern
By Steven Black
Introduction
This document describes an object-oriented design pattern called Hooks and Anchors. Consider this a discussion document.
Hooks and anchors is an object society that I first designed and implemented in 1996 and used ever since. It has served me well in numerous desktop and internet applications.
Here's how this paper goes: first we start by examining the class structure of Hooks and hook subclasses.
Next we'll discuss the Anchor class. Anchors, as with real boat anchors, are a type of Hook.
This is a cool conceptual nuance -- why would an Anchor be a subclass of Hook? The placement of hooks and anchors under a common hook superclass, is explained.
Next we'll look at how particular design patterns are conducive to implementations involving with hooks and anchors.
Background
Most good object-oriented frameworks can be described as skeletal structures designed for extension. Developers evolve their base framework(s), refining and extending them over time.
Similarly, all the root frameworks give you so-called hot spots, known as Hook Methods or, as Gamma and Helm call them, Template Methods. To varying degrees, the framework(s) you use also supply a philosophy about usage and extension. Typically the suggested philosophy is: you hook the framework's hook methods to suit your implementation-specific needs.
In the Hooks and Anchors design pattern, the hook is implemented with a hook object society, which is designed at the outset to fully encapsulate work of the hook method. The hook method, therefore, reduces to a hook operation that delegates program flow to the society of hook objects.
In other words, at the program's critical junctures, instead of adding code to specifically named hook methods, Hook and Anchor pattern implementations use a hook class instead, which hides a society of hooks and anchors, placing code in a polymorphically-named action methods of fine grained hook classes.
In the Hooks and Anchors design presented here, the hook class provides a ::Process()
method that is called by clients. The ::Process()
method handles pre- and post-processing, calling a protected method named ::Execute() method wherein the hook's real deed is done.
Therefore, A Hooks and Anchors society is the sort of thing you call from a hot spot, usually instead of most or all the code you would normally place there.
The crux of the matter is this: since we're talking about VFP here, the Hooks and Anchors society is, of course, natively engineered to be metadata-driven in both composition and execution.
Class Structure
This section focuses on the morphology of the Hooks and Anchors solution framework. We develop the concept of hook chains and anchors, and how they can be simply combined in the same class hierarchy.
UML Development
Using UML notation, Figure 1 illustrates an atomic concept of object-oriented software: two separate classes with a one-way, 1:1 relationship between them. In this case, SomeClass holds a reference to SomeHook through the SomeClass.oHook member property
Figure 1. a fundamental object micro society.
Figure 2 shows a variation of the relationship in Figure 1, except now assume that two collaborating classes are descendents of a common Parent Class. This assumption may seem like a stretch. If the classes descend from a common parent, then the semantics of the relationship can easily be refactored and generalized into the parent class. If on the other hand the classes do not descend from a common parent, then we can get the same effect with code duplication. For now, lets agree to keep things simple, and descend from a common parent, if only for convenience.
Figure 2. First-pass generalization of the object micro society to a common ancestor.
Into the Parent Class of Figure 2 we can now refactor upwards, moving the details of the relationship into the parent class, which leads to the hierarchy illustrated in Figure 3. Its not obvious upon first glance, but the hierarchy in Figure 3 excels at providing components for object chains, like for example the one illustrated in Figure 4. The key to this is the self-referential relationship link on the Hook Parent Class as illustrated in Figure 3.
Figure 3. At left, all the descendants of the Hook Parent Class inherit the self-referential relationship, and this makes them easily assembled together into chains. The diagram thus reduces abstractly to the one on the right, representing a single class with self-referential semantics.
In my experience great things can happen when you sensibly use self-referential structures like those of Figure 3 to create solutions that execute objects sequentially, as illustrated in the class diagram in Figure 4, and also illustrated in the corresponding stair-shaped sequence diagram illustrated in Figure 5.
Figure 4. A chain of N objects linked together. Normally control passes from instances of Hook1 to Hook2 to Hook3, and so on, normally without any intervention from a controlling object.
Figure 5. The sequence diagram for a chain of objects exhibits a stair-shaped interaction pattern.
Implementation Qualities of Hook Chains
In this section we discuss the types of implementations that naturally lend themselves to solutions by hook chains, then well list some of the Gamma and Helm design patterns that can be created with hook chains, and finally well as list and explain some of the problems Ive encountered when implementing hook chains.
Applications of Hook Chains
According to Riel [2]. the stair shaped interaction of hook chains, as illustrated in Figure 5, is expected to be appropriate in the following situations.
- When operations have a strong connection
- Consists-of hierarchy: country-state-city
- Info hierarchy: document-section-topic
- Temporal: ad-order-invoice-deliver
- Conceptual: person-customer-key account
- Operations always in the same order
- Decentralized programming is possible
Abstraction of Hook Chains into Fundamental Design Patterns
When we analyze an object chain of two or more objects, we see that it can be applied in several of the most common structural and behavioral Gamma and Helm Design Patterns, some of which are listed in Table 1.
Gamma and Helm Pattern [1] | Intent |
---|---|
Bridge | Decouple an abstraction from its implementation so that the two can vary independently. |
Chain of Responsibility | Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it. |
Composite | Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly. |
Decorator | Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality. |
Strategy | Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it. |
Table 1. Some Patterns implementable with "chained structures" abstractions.
Some Notable Problems with Hook Chains
Given the usefulness of chained structures for creating a variety of micro- architectures, here is are some of the problems that come with using chains. These problems typically get worse as the chain gets longer and/or more diverse:
Creating and initializing the chain Hook chains can be short or long, and regardless they need to be assembled, sequenced, and configured. This can be done with some sort of abstract factory, and be aided if the hooks know how to chain themselves together. Then consider the desirability of creating chains from metadata. Therefore the process of creating chains needs abstracting.
Chain execution control (pre-empting, escaping, re-entering) looking at Figure 5, its hard to imagine invoking an automatic sequential chain of activity without all the control required to properly handle errors and other execution issues like escaping and re-entering the chain.
Coupling between chain elements Coupling comes in many forms, like control coupling (when execution order is important), content coupling (when one object messes directly with the internals of another), data coupling (when specific data needs to be shared between objects), external coupling (when an object depends on other external objects), stamp coupling (when two components modify or access data in the same object). All these coupling hinder to some degree the integrity, flexibility, and reusability of the chain and its elements.
Selective iteration of the chain The assumption that every object will fire does not always hold. How then to execute a particular sequence of objects if they are physically chained in another sequence?
Returning multiple values How do you return multiple values, or aggregate the return values, from a single chain of execution that may be composed of several distinct operations.
Error and notification handling How to handle errors and notifications in a hook chain?
Extending and Enhancing Hook Chains with Hook Anchors
In the previous section we show that hook chains have both good and not-so-good qualities. Can some of the not-so-good qualities be mitigated?
The complement of the stair-shaped interaction diagram of Figure 5 is the fork-shaped interaction pattern, illustrated in Figure 6. This illustrates a prototypical so-called god-object situation, where behavior is centralized in a single object that manipulates the other objects.
Figure 6. When an object has direct control over other objects, the sequence diagram of the interaction exhibits a fork-shaped interaction pattern.
Here are some of the situations that call for a god-objects fork interaction patterns:
When operations can change order
When new operations could be inserted
When centralized control is necessary and unavoidable
Note as well that holding many concurrent references to external objects pretty much comes with being a god-object.
Lets extend the class hierarchy of Figure 3 and enhance it with a new class for coordination and control purposes. In Figure 7, this new god-object class is the Hook Anchor class, which could possesses, among other things:
A .aHook member array (or if in VFP 8, a reference to collection) to hold zero or more objects of any subclass of Hook Parent Class. Thus a chain of objects can be built in two different ways: as a simple chain of independent objects, or aggregately referenced by an instance of Hook Anchor which will iteratively invoke them.
A LOAD() method, called upon INIT(), which takes care of creating and assembling objects, or chains of objects.
The ability to iterate through a list of objects and, between each, test whether to stop the hook iteration.
Figure 7. Extending the class hierarchy, this time introducing Anchors, which are designed to address the problems of free-form hook chains by providing the ability to concurrently hold many hook objects as well as iterate them in custom and controlled ways.
A notable concept to grasp from Figure 7 is that Anchor classes are hooks. Since hooks can reference hooks, and hooks can be chained, all this applies to Anchors as well.
Abstraction of Hook Anchors into Fundamental Design Patterns
Table 2 below lists of the additional Gamma and Helm design patterns especially the creational ones that the Hook Anchor classes bring, to some degree, to the table.
Gamma and Helm Pattern [1] | Intent |
Abstract Factory | Provide an interface for creating families of related or dependent objects without specifying their concrete classes. |
Builder | Separate the construction of a complex object from its representation so that the same construction process can create different representations. |
Factory Method | Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses. |
Iterator | Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation. |
State | Allow an object to alter its behavior when its internal state changes. The object will appear to change its class. |
Table 2. Additional Patterns implementable with Hook Anchor abstractions.
Interface
Here are some of the notable methods of the hook class hierarchy, and of the anchor branch of subclasses.
Property or Method | Reason |
| A member which stores a SCATTER NAME object of the metadata record responsible for its creation. |
| A member to hold the next object in the hook chain. |
| This is the only method the clients will call. |
| This method is called by the |
| This method fires at the end of |
| The action method of the hook, wherein the hook actually accomplishes what it is designed to do. |
| This method is used to build the hook chain. The semantics are as follows: If This.oHook is an object, then pass the passed parameter to |
| Array member of hook objects to anchor and iterate. |
| Loads the hooks (or hook chains) from metadata specified by the passed parameters. |
| Iterates through the collection of attached hooks and hook chains, calling the Process() method of each. |
| Provides an escape mechanism for exiting the iteration. |
| Adds an object to the collection of hooks. |
Upsides
Here are some advantages of Hooks and Anchors
- Runtime configurable.
- Process clones and variants are easily created.
- Modular design, construction, and implementation.
Downsides
Here are some downsides with Hooks and Anchors as presented here.
It takes some getting used to, and its not easy to abstract all the operations of a process into logical independent hooks, and to thereafter anchor them and iterate them properly.
Its more complex than simple subclassing and filling hot-spot methods.
Plugging in a new object involves steps that are vulnerable to configuration mistakes without tool support. Using BROWSE on a VFP configuration table is only good for those who already know what they are doing.
Applications Putting it all together
This section serves as an outline of what will be discussed in the session, and quite probably be included with sample programs on the conference CD, or downloadable from my website at http://stevenblack.com.
- Example: Parsers -- Cleaning up HTML (sample with code)
- Processes -- A Shopping Cart
- Localization INTL
Conclusion
This paper briefly describes the major elements of the Hooks and Anchors solution framework. Approaching problems this in this way isnt for everyone, nor is suitable for all situations. For real hot spots, where your application can be expect evolution stress, it can be just the ticket.
Hooks and Anchors Design Pattern, Example 1
MS Excel "Save As HTML" cleanup.
Given you've got Odious Crap HTML From MS Excel like this (view its source), how to cleanse it so it's generic and clean like, say, this (view source)??
Simple. You set up an object society like the one illustrated below, whose life and execution is controlled by HooksConfig.DBF records, listed in the table below the diagram.
Complete source code here: HooksAndAnchors.ZIP
Given Garbage HTML, here's the hook society that cleans it...
....All created and invoked by this simple VFP code....
*-- This sample shows the cleansing of the *-- odious HTML you get from an MS Excell "Save as HTML". *-- *-- Environment SET PROCEDURE TO HooksAndAnchors additive SET PROCEDURE TO ParserHooks additive LOCAL lcHTML, loChain *-- Processing lcHTML=FILETOSTR("crapfromexcel.htm") loChain=CREATEOBJECT("HookAnchor","Excel Paste","Root") loChain.Process( @lcHTML) && clean this HTML pig! *-- Show the results STRTOFILE(lcHTML, "CleanHTMLFromExcel.HTM") && Tada SHELLEXEC("CleanHTMLFromExcel.HTM")
....which is orchestrated by these records in HooksConfig.DBF.
Cset | Ctype | Cclass | Clibrary | Lactive | Seq | Properties |
---|---|---|---|---|---|---|
Excel Paste | ================== | .F. | 0 | |||
Excel Paste | Root | BodyContentsOnly | ParserHooks.prg | .T. | 10 | |
Excel Paste | Root | StripContents | ParserHooks.prg | .T. | 12 | <![if,<![endif]> |
Excel Paste | Root | StripContents | ParserHooks.prg | .T. | 13 | <!––,––> |
Excel Paste | Root | MiscCharsRemove | ParserHooks.prg | .T. | 14 | x: v: |
Excel Paste | Root | HTMLTidy | ParserHooks.prg | .T. | 20 | |
Excel Paste | Root | DOMAnchor | HooksAndAnchors.prg | .T. | 50 | |
Excel Paste | DOMAnchor | KillNodesHook | ParserHooks.prg | .T. | 50 | cCollQuery=//table cKill=col |
Excel Paste | DOMAnchor | KillAttribsHook | ParserHooks.prg | .T. | 100 | cCollQuery=//table cKill=bgcolor,width,cellpadding,class,str |
Excel Paste | DOMAnchor | KillAttribsHook | ParserHooks.prg | .T. | 200 | cCollQuery=//td | //th cKill=bgcolor,width,valign,class,height,num |
Excel Paste | DOMAnchor | KillAttribsHook | ParserHooks.prg | .T. | 300 | cCollQuery=//tr cKill=bgcolor,height,class |
Bibliography
[1] Gamma, E., Helm, R., Johnson, R, and Vlissides, J. (1994), Design Patterns, Elements of Object Oriented Software, Addison Wesley, Reading, MA, ISBN 0-201-63361-2.
[2] Riel, A (1996), Object Oriented Design Heuristics, Addison Wesley, Reading, MA, ISBN 0-201-63385-X.
[3] Class Composition for Specifying Framework Design, S Demeyer, M Rieger, TD M, E Gelsema (http://scg.unibe.ch/archive/papers/Deme99bClassComposition.pdf)