Rice University - Comp 212 - Intermediate Programming

Fall 2002

Lecture #08 - To Cook or Not To Cook... Decoupling Data Structures from Algorithms - The Visitor Pattern


0. To Cook or Not To Cook

Our household consists of my wife and myself.  My wife is a vegetarian.  She only cooks and eats vegetarian food.  I am carnivorous.  I cook and eat meat!
If my wife wants to eat broccoli and cheese, she can learn how to cook  broccoli and cheese.
If she wants corn of the cob, she can learn how to cook corn on the cob.  The same goes for me.
If I want to eat greasy hamburger, I can learn how to cook greasy hamburger.
If I want to eat fatty hotdog, I can learn how to cook fatty hotdog.
Every time we want to eat something new, we can learn how to cook it.  This requires that my wife and I to each have a very big head in order to learn all the recipes.  But wait, there are things out there called restaurants!  These are very special kinds of restaurants catering only to vegetarians and carnivores like people in our household.  These restaurants only have two dishes on their menus: one vegetarian dish and one meat dish.  All my wife and myself have to do is to know how to call such a restaurant to order our favorite dish.
My wife will only order the vegetarian dish, while I will only order the meat dish!
How do we model this set up?
In the design pattern language, our household is called the host, and the restaurants are called visitors.
To paraphrase Shakespeare, it's a matter of to cook or not to cook!

abstract class Eater {
  abstract Object order(IRestaurant r);
}
interface IRestaurant {
   Object cookVeggie();
   Object cookMeat();
}
class Vegetarian extend Eater{
  Object order(IRestaurant r) {
     return r.cookVeggie();
  }
}
class Carnivore extend Eater{
  Object order(IRestaurant r) {
     return r.cookMeat();
  }
}
class ChezAlan implements IRestaurant {
   Object cookVeggie() {
       return "Brocoli and Cheese";
   }

   Object cookMeat() {
       return "Greasy Hamburger";
   }
}
class ChezZung implements IRestaurant {
   Object cookVeggie() {
       return "Corn on the cob";
   }

   Object cookMeat() {
       return "Fatty Hotdog";
   }
}

 

interface

An interface is like an abstract class with the special constraint that all methods must be pure abstract and have no code bodies.  By definition, an interface and its methods are always public and abstract.  Thus one need not declare them using the public and abstract attributes.  An interface is used to specify a type without any concrete implementation.  When a class implements an interface, it must provide concrete code for each of the methods in this interface.  In Java, a class can extends only one other class, but can implement as many interfaces as it sees fit.  This provides a form of multiple-inheritance that has proven to be quite flexible and useful in many advanced OO designs.

The Union Pattern

Suppose I am faced with the problem of computing the areas of geometrical shapes such as rectangles and circles.  I need to build objects that are capable of computing these areas.  The variants for this problems are the infinitely many shapes: rectangles, circles, etc.  What I do is define concrete classes such as Rectangle and Circle, and make them subclasses of an abstract class, called AShape, which has the abstract capability of computing its area.  This is an example of the simplest yet most fundamental OO design pattern called the Union Pattern. 

The Union Pattern is the result of partitioning the sets of objects in the problem domain into disjoint subsets and consists of

A client of the Union Pattern uses instances of the concrete subclasses (Variant1, Variant2), but should only see them as AClass objects.   The client class code should only concern itself with the public methods of AClass and should not need to check for the class type of the concrete instances it is working with.  Conditional statements to distinguish the various cases are gone resulting in reduced code complexity, making the code easier to maintain.

NOTE: the term Union Pattern is not part of the standard pattern language in the design pattern community.  Such a pattern is taken for granted in all OO design.  Here we give it a name to facilitate the discussion of its use in all future designs.


I. Functional List Structure

Recall the functional Scheme-like list data structure, AList.

Each time we want to compute something new, we have to edit each class and add appropriate methods to each class.  

Is there a way to add new behavior to AList (and its variants) without touching any of the existing code, leaving everything that has been written so far unchanged?


II. Functional List Framework

The key is to encapsulate the variant behaviors into a separate union pattern.  Here, the variant behaviors are the infinitely many algorithms (i.e. computations) that we want to operate on  AList .  The invariant behaviors are the methods getFirst() and getRest().  For AList to execute any of these algorithms, we just need to add to the design of AList one more method and never have to modify anything ever again!

This is an example of what is called the Visitor Pattern.  Class AList is calle the host and its method execute() is called a "hook" to the IListAlgo visitors.  Via polymorphism, AList knows exactly what method to call on the specific IListAlgo visitor.  As such, AList is said to be a framework for reusing and extending the list structure.  It is capable of executing any algorithm, past, present, and future, that conforms to the IListAlgo interface.  IListAlgo is said to define a protocol for communication between a list and algorithms on a list.

About Frameworks

The following is a direct quote from the Design Patterns book by Gamma, Helm, Johnson, and Vlissides (the Gang of Four - GoF).

"Frameworks thus emphasizes design reuse over code resuse...Reuse on this level leads to an inversion of control between the application and the software on which it's based. When you use a toolkit (or a conventional subroutine library software for that matter), you write the main body of the application and call the code you want to reuse. When you use a framework, you reuse the main body and write the code it calls...."

The linear recursive structure (AList) coupled with the visitors as shown in the above is one of the simplest, non-trivial, and practical examples of frameworks.  It has the characteristic of "inversion of control" described in the quote. It illustrates the so-called Holywood Programming Principle: don't call me, I will call you. Imagine the AList union sitting in a library. When we write an algorithm on an AList  in conformance with its visitor interface, we are writing code for the AList to call and not the other way around. By adhering to the AList framework's protocol, all algorithms on the AList can be developed much more quickly. And because they all have similar structures, they are much easier to "maintain". The AList  framework puts polymorphism to use in a very effective (and elegant!) way to reduce flow control and code complexity.

Visitor Code Examples

/**
 * Computes the length of the AList host.
 */
public class GetLength implements IListAlgo {
    /**
     * Singleton Pattern.
     */
    public static final GetLength Singleton = new GetLength();
    private GetLength() {
    }
    /**
     * Returns Integer(0).
     * @param host an EmptyList
     * @param inp not used
     * @return Integer.
     */
    public Object emptyCase(AList host, Object inp) {
        return new Integer(0);
    }
    /**
     * Return the length of the host's rest plus 1.
     * @param host an NEList
     * @param inp not used.
     * @return Integer
     */
    public Object nonEmptyCase(AList host, Object inp) {
        Integer restLen = (Integer)host.getRest().execute(this, null);
        return new Integer(1 + restLen.intValue());
    }
 
/**
 * Assumes the AList host contains Integer, computes the minimum of the all
 * the Integer objects in the host.
 */
public class GetMin implements IListAlgo {
    /**
     * Singleton Pattern.
     */
    public static final GetMin Singleton = new GetMin();
    private GetMin() {
    }
    /**
     * Throws an IllegalArgumentException.
     * @param host an EmptyList
     * @param inp not used
     * @return does not return.
     * @exception IllegalArgumentException
     */
    public Object emptyCase(AList host, Object inp) {
        throw new IllegalArgumentException("EmptyList has no data!");
    }
    /**
     * Passes the host's data to the host's rest and asks for help to compute
     * the minimum element in the host.
     * @param host an NEList
     * @param inp not used
     * @return Integer
     */
    public Object nonEmptyCase(AList host, Object inp) {
        return host.getRest().execute(GetMinHelper.Singleton, host.getFirst());
    }
 
public class GetMinHelper implements IListAlgo {
    /**
     * Singleton Pattern.
     */
    public static final GetMinHelper Singleton = new GetMinHelper();
    private GetMinHelper() {
    }
    /**
     * Returns inp.
     * @param host an EmptyList
     * @param inp Integer the minimum of the preceding list.
     * @return Integer
     */
    public Object emptyCase(AList host, Object inp) {
        return inp;
    }
    /**
     * Recurs by passing the mimum between the host's first and inp, the
     * accumulated current minimum.
     * @param host an NEList.
     * @param inp Integer the minimum of the preceding list.
     * @return Integer
     */
    public Object nonEmptyCase(AList host, Object inp) {
        int f = ((Integer)host.getFirst()).intValue();
        int i = ((Integer)inp).intValue();
        return host.getRest().execute(this, new Integer(Math.min(f, i)));
    }

 

III. The Visitor Pattern

The visitor pattern is a framework for communication and collaboration between two union patterns: a "host" union and a "visitor" union.  An abstract visitor is usually defined as an interface in Java.  It has a separate method for each of the concrete variant of the host union.  The abstract host has a method (called the "hook") to "accept" a visitor and leaves it up to each of its concrete variants to call the appropriate visitor method.  This "decoupling" of the host's structural behaviors from the extrinsic algorithms on the host permits the addition of infinitely many external algorithms without changing any of the host union code.  This extensibility only works if the taxonomy of the host union is stable and does not change.  If we have to modify the host union, then we will have to modify ALL visitors as well!

NOTE: All the "state-less" visitors should be singletons.

 

dxnguyen@cs.rice.edu
Copyright 2002, Dung X. Nguyen - All rights reserved.