TweetFollow Us on Twitter

Design Patterns Tutorial

Volume Number: 13 (1997)
Issue Number: 4
Column Tag: Programming Techniques

The Tao of Design

By John Schettino

Using Design Patterns to help reuse the structure of your code

The Tao

I know what you're thinking not another "Eastern Mysticism as applied to Object Oriented Programming" article. Don't worry, although I'm going to begin with an eastern mysticism example, I'll spend most of the article presenting real examples of design patterns in C++. You're now probably wondering what is a design pattern? To answer that question we need to consider Tai Chi, Fibonocci, and the Tao.

I used to study Tai Chi, a martial art and a philosophy towards life. As I was learning the "form" (a series of body positions) my teacher would often tell us stories that illustrated the principals of Tai Chi. One day he began talking about Fibonocci. This mathematician and philosopher studied Nature and numbers. What he discovered, and Mandelbrot later refined, was that nature was really into reuse. There are patterns to both numbers and organisms. My teacher related Fibonocci sequences to Tai Chi by pointing out the many patterns within the form: how large movements are built from similar small movements in a Fibonocci sequence, how positions repeated in slight variations, and so on. It was all rather enlightening. What of the Tao? The Tao-te Ching roughly translates into "the way." It is the essential unifying element of all that is. It is a way of perception and living, as are using design patterns. Once you grasp the concept of design patterns you will see how and where to apply them. In essence, you'll be living the Tao of Design.

Design patterns are an attempt to express the sameness of design solutions in object oriented programs. It is really a way of thinking about a problem and the design of that problem's solution. Think about your own programming style. A large portion of it is most likely the way you structure your solutions, rather than the minute details of your code. If you could reuse the structure from one project to the next, you would gain all of the benefits of reuse (savings of time, higher quality, etc.) on a larger scale than simply reusing classes or functions. This is the central premise behind the book "Design Patterns" by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides.

Design Patterns

The book is an attempt to present the concept behind design patterns and describe several patterns that you can use. It is not intended as an introductory text on object oriented programming! The authors state up front that you should be proficient in at least one object oriented language (the examples are in Smalltalk and C++), and that you have object oriented design experience. To understand the book you must be familiar with such concepts as types, polymorphism, and interface versus implementation inheritance. This is not a light book. If you don't know object oriented design this is not the place to start. If, on the other hand, you have been involved with object oriented design and/or programming and feel that you could get more reuse or elegance out of your efforts, this is precisely the place to start.

The book begins with a description of the concept of design patterns and the motivation for using them. The idea is sound. We all reuse our own personal set of design solutions that we've built up through years of experience. We should study those solutions to discover the patterns within them, and then formalize those patterns into reusable classes.

What the authors propose is that we develop a common way of referring to and specifying designs. Not only do they propose it, they then give a detailed example of how to apply design patterns in an application. The example is somewhat academic, as is the entire book, but don't let that stop you from learning from it. Following the example are the 23 patterns themselves. They are organized into three categories: creational, structural, and behavioral. Each category contains several patterns.

Creational patterns abstract the instantiation process. When you are designing complex object oriented systems you often rely on composite objects along with class-based inheritance. Creational patterns are used to delegate instantiation, abstract behavior, and hide instantiation and composition details.

Structural patterns focus on the composition of classes and objects into larger structures. They deal with run-time compositions that are more dynamic than traditional multiple inheritance, object sharing and interface adaptation, and dynamic addition of responsibilities to objects.

Behavioral patterns deal with encapsulating algorithms and managing or delegating responsibility among objects. They focus more on communication and interaction, dynamic interfaces, object composition, and object dependency.

Many design problems fall into one or more of these categories. Typically, one must create several objects to represent the data in a design and decide how those objects interact and behave. When design patterns are applied to these problems you create more general, extensible, and reusable solutions than you might otherwise have done. You're not doing this extra work simply to feel good about yourself. You're helping yourself in the future when you must maintain this code or solve a similar problem.

Each pattern is described from several perspectives:

• Intent: a description of the purpose the pattern serves.

• Also Known As: common names used for the pattern, such as Wrapper instead of Adapter.

• Motivation: why you would use the pattern.

• Applicability: when to use the pattern.

• Structure: one or more diagrams describing the classes in the pattern and how they relate.

• Participants: a description of each of the classes used in the Structure section.

• Collaborations: a description of the run-time interaction between instances of the classes used in the Structure section.

• Consequences: a description of the trade-offs and issues with using the pattern.

• Implementation: a description of the implementation issues. The authors tell you what to avoid and what to consider when implementing a pattern.

• Sample Code: implementations of classes illustrating the pattern in Smalltalk and C++.

• Know Uses: a description of some well-known commercial and academic applications, toolkits, and languages that use the pattern.

• Related Patterns: a list of the patterns that may be implemented by, use, or interact with this pattern.

The authors close each section with a discussion of the type of problems the category of patterns is attempting to solve. This section is most useful to those with limited design experience. Old hands will find that they can think of many situations where they could (or perhaps already do) apply the patterns.

You'll gain three new skills from reading the book. First, you'll know more about design patterns themselves. You'll even know how to identify and classify them. Second, you'll know the patterns and classes presented. This can act as a standard nomenclature when you're discussing designs with other programmers. Third, you'll have 23 patterns to use in your own programs. Some of these you will have already discovered yourself while others will probably be complete revelations to you.

Patterns in Action

Let's put the principals of design patterns into action. Illustrating all 23 patterns presented in the book is beyond the scope of this article, but I'll present an example that applies a pattern from each category to help motivate you to explore the topic further. Since these are design patterns, we need a problem to solve. I'm going to take the easy road and select a completely made-up problem - tree manipulation. We will build two tree structures containing file names found in a Macintosh file directory and perform some operations on it.

Problem Statement

The program traverses a Macintosh file directory and the subdirectories in it to create a single tree structure containing all the names of the files and folders visited. The names are then retrieved by traversing the tree.

This is not exactly rocket science. There are plenty of ways of doing this without resorting to objects at all, let alone the design patterns described in the book. The example is simple so that you can focus on applying the patterns.

Design

We are talking about design patterns, so let's begin with a design before we start coding. We want some form of tree that consists of nodes representing the file names. Using a tree is an easy way to get a sorted list. To abstract the building process we'll apply a creational pattern called Builder. This allows us to hide the internal structure of the tree being built, and to build different types of trees using the same program.

Once the trees are built we must access them to retrieve the file names. A behavioral pattern called Iterator allows us to encapsulate the access algorithm such that the main program does not know the tree structure or the algorithm used to traverse the tree. This is in contrast to the more common recursive tree walking algorithms you'd consider first. Using an Iterator to traverse trees means you have to use stacks and queues to maintain your current location. Rather than implement our own stack and queue objects, we can take advantage of a structural pattern called Adapter to extend a list object to include the additional behaviors of a stack and a queue. We'll take advantage of several templated classes from the C++ Standard Template Library (STL) for this.

We'll use three classes for the project. The first class hierarchy represents nodes in the tree. The TreeNode base class defines the common interface for our tree nodes and a subclass implements a simple binary tree node. This isn't a design pattern - just good object oriented design. The second class hierarchy is for a TreeIterator class that retrieves the nodes of a tree. Two subclasses of the TreeIterator class implement depth-first and breadth-first traversals of the tree.

Finally, the TreeBuilder class hierarchy builds trees consisting of TreeNodes. One subclass of this class builds binary trees, the other builds height-balanced binary trees. The main program uses instances of both TreeBuilder subclasses to create two different trees containing instances of the TreeNode class. It then retrieves and prints the file names from each tree using instances of both of the TreeIterator classes. It performs all these operations without explicit knowledge of the internal representations of the trees or the algorithms used to traverse them.

Patterns

Three patterns are used in the design: Iterator, Adapter, and Builder. Let me summarize each of these patterns and how they are applied to the problem.

An Iterator is an example of a behavioral pattern. It is a class that is instantiated with a compound data structure. Each time you call a member function of the instance it returns the next element of the structure. A null value is returned once all elements have been accessed. In our project a tree consisting of several nodes is the compound data structure. The elements returned are the nodes in the tree. We use the Iterator to encapsulate the tree traversal algorithm in a class and remove that code from our main program. That means the main program does not know how the tree is organized. It isn't changed if the tree implementation changes or if we discover a more efficient algorithm for traversing the tree.

An Adapter is an example of a structural pattern. It adds new interfaces to an existing object or class to create a new object or class with additional behaviors. We use STL stacks and queues in our TreeIterator subclasses. Stacks and queues must store elements in a list, so a list class can be adapted by adding new member functions such as push() and pop().

A Builder is an example of a creational pattern. It encapsulates the actual building of a composite data structure and hides the details of how that structure is composed by defining one or more member functions to add elements to the composite structure. You can create general purpose data collection routines that accept instances of a Builder class and then build different forms of the composite data structure. In our project the TreeBuilder class builds trees. Because we use a Builder we can structure the trees in many different ways without affecting the main program.

Implementing The TreeNode Class

The TreeNode class represents a node in a tree. The base class defines the minimal interface for a TreeNode.

typedef class TreeNode * TreeNodePtr;
typedef class BinaryTreeNode * BinaryTreeNodePtr;

class TreeNode {
 friend ostream& operator<< (ostream& os, 
 TreeNode& t);
 friend ostream& operator<< (ostream& os, 
 const TreeNodePtr& t);
public:
 virtual const char * output(void) {};
 virtual int compare (const TreeNode& otherNode)
 {};
};

All TreeNode subclasses must implement the output() and compare() member functions. Use output() to get a string form of the node suitable for printing. Use compare() to determine if this TreeNode is less than, equal to, or greater than another TreeNode. We use a class hierarchy so we can create new subclasses that hold more complex data in a node.

The BinaryTreeNode subclass of TreeNode represents a node in a binary tree.

class BinaryTreeNode: public TreeNode {
public:
 BinaryTreeNode(const char *name)
 { _fname = new char[strlen(name)+1];
  strcpy(_fname, name);
  _leftChild = _rightChild = 0; }
 virtual const char* output(void) 
 { return _fname; }
 virtual int compare (const BinaryTreeNode&
 otherNode)
 { return strcmp(_fname, otherNode._fname); }
 void setLeftChild(const BinaryTreeNodePtr l) 
 { _leftChild = l; }
 void setRightChild(const BinaryTreeNodePtr r) 
 { _rightChild = r; }
 BinaryTreeNodePtr leftChild() 
 { return _leftChild; }
 BinaryTreeNodePtr rightChild() 
 { return _rightChild; }
private:
 char *_fname;
 BinaryTreeNodePtr _leftChild;
 BinaryTreeNodePtr _rightChild;
};

It uses a string stored in _fname as the node value. The output() member function simply returns _fname. The compare() member function used strcmp() to compare the values of the two nodes. We also define member functions to get and set the left and right child pointers stored in the node. This allows other classes to build and traverse trees consisting of these nodes.

Implementing The TreeNodeIterator Class

The TreeNodeIterator class hierarchy implements the Iterator design pattern. It is a class that is instantiated with a compound data structure. Each time you call a member function of an instance of this class it returns the next node of the tree. The base class defines the interface.



class TreeNodeIterator {
public:
 virtual TreeNodePtr operator++ (int) {};// next 
 virtual TreeNodePtr current() {}; // get current
 virtual TreeNodePtr next() {};    // get next
 virtual void reset() {}; // reset
};

This class implements both the ++ increment operator and the current() and next() member functions to retrieve nodes in the tree. It also includes a reset() member function so the same Iterator can be used to extract elements multiple times. We use a class hierarchy so we can provide two ways of iterating through the tree. The DFTreeNodeIterator subclass implements a depth-first access of the tree. This retrieves nodes in sorted order if it is accessing a binary tree.

The DFTreeNodeIterator returns nodes in sorted order.

// Depth-First BinaryTree Node Iterator
class DFTreeNodeIterator : public TreeNodeIterator {
public:
 DFTreeNodeIterator(BinaryTreeNode &tree);
 virtual TreeNodePtr operator++ (int);
 virtual TreeNodePtr current();
 virtual TreeNodePtr next();
 virtual void reset();
private:
 BinaryTreeNode *_origTree, *_tree;
 // use an STL Stack
 stack<list<BinaryTreeNodePtr> > _pendingNodes;
 void _pushLeft();
};

The private members of the class add a couple of pointers to nodes in the tree. The *_origTree pointer holds the root node of the tree used to instantiate the iterator. This is used to reset the iterator. The *_tree pointer points to the current node in the tree. A STL stack (_pendingNodes) is used to hold the pending nodes as the tree is traversed. This is similar to using the program stack in a recursive traversal of the tree. Finally, a member function that pushes nodes onto the stack is declared. Let's look at the implementation of the member functions next.

// - DFTreeNodeIterator member functions -
DFTreeNodeIterator::
DFTreeNodeIterator(BinaryTreeNode &tree)
{
 _origTree = _tree = &tree;
 _pushLeft();
}

The constructor takes a BinaryTreeNode as its only parameter. The two pointers stored in the are initialized to this value. Then the _pushLeft() member function is used to set the _tree pointer to the first node to return. The iterator is now ready to return successive nodes of the tree.

void
DFTreeNodeIterator::_pushLeft()
{
 while (_tree->leftChild())
 {
 _pendingNodes.push(_tree);
 _tree = _tree->leftChild();
 }
}

The _pushLeft() member function traverses the tree to the leaf node on the left hand side, pushing each visited node onto the _pendingNodes stack. Once the end of the tree is reached that node is consider the current node. Let's look at the three member functions that return nodes.

TreeNodePtr
DFTreeNodeIterator::operator++ (int)
{
 TreeNode* theNode = current();
 next();
 return theNode;
}

The ++ operator retrieves the current node, moves the to the next node, and then returns the current node. This acts just like the postfix ++ operator in C++.

TreeNodePtr
DFTreeNodeIterator::current ()
{
 return _tree;
}

Because _tree always points to the current node in the tree, the current() member function just returns it.

TreeNodePtr
DFTreeNodeIterator::next()
{
 if (_tree)
 {
    // follow right child ptr
 if (_tree->rightChild())
 {
 _tree = _tree->rightChild();
 _pushLeft();
 }
 else if (_pendingNodes.size() > 0)
 { 
 // end of branch, pop and return node itself
 _tree = _pendingNodes.top();
 _pendingNodes.pop();
 }
 else
 _tree = 0; // all done
 }
 return _tree;
}

The next() member function traverses the tree to the next node. The current node's right child is accessed. If it exists then the tree is traversed thru the left child from that node, again stacking up all visited nodes. If there is no right child then the stack is popped, and the popped node becomes the current node. If there are no nodes in the stack then the tree has been traversed completely.

void
DFTreeNodeIterator::reset()
{
 _tree = _origTree;
 _pushLeft();
}

The reset() member function restores _tree to the originally supplied value and then calls _pushLeft(). This restores the iterator to its initial state.

The BFTreeNodeIterator subclass implements a breadth-first access of the tree. This retrieves nodes in height order (in other words, all nodes at height N of the tree are returned before moving to nodes at height N+1.)

// Breadth-First BinaryTree Node
class BFTreeNodeIterator : public TreeNodeIterator {
public:
 BFTreeNodeIterator(BinaryTreeNode &tree);
 virtual TreeNodePtr operator++ (int);
 virtual TreeNodePtr current();
 virtual TreeNodePtr next();
 virtual void reset();
private:
 BinaryTreeNode *_origTree, *_tree;
    // use an STL queue
 queue<list<BinaryTreeNodePtr> > _pendingNodes;
 enum {IO_node, IO_left, IO_right} _current;
};

The private members of the class add a couple of pointers to nodes in the tree as was done in the other. A STL queue (_pendingNodes) is used to hold the pending nodes as the tree is traversed. Since we traverse the tree by level we use a queue to revisit nodes. Finally, an enumeration is used to keep track of the state of the current node in the retrieval process. The implementations of the member functions of this class differ somewhat from the other, because it returns nodes in a different order.

// - BFTreeNodeIterator member fns -
BFTreeNodeIterator::
BFTreeNodeIterator(BinaryTreeNode &tree)
{
 _origTree = _tree = &tree;
 _current = IO_node;
}

TreeNodePtr
BFTreeNodeIterator::operator++ (int)
{
 TreeNode* theNode = current();
 next();
 return theNode;
}

TreeNodePtr
BFTreeNodeIterator::current ()
{
 if (!_tree) return 0;
 
 switch (_current) {
 case IO_node:
 return _tree;
 case IO_left:
 return _tree->leftChild();
 case IO_right:
 return _tree->rightChild();
 }
 
}

TreeNodePtr
BFTreeNodeIterator::next()
{
 BinaryTreeNodePtr c;
 switch (_current) {
 case IO_node:
 if (_tree->leftChild()) _current = IO_left;
 else if (_tree->rightChild()) 
 _current = IO_right;
 else if (_pendingNodes.size() > 0)
 {
 _tree = _pendingNodes.front();
 _pendingNodes.pop();
 _current = _tree->leftChild() ? 
 IO_left : IO_right;
 }
 else _tree = 0;
 return current();
 case IO_left:
 c = (BinaryTreeNodePtr) current();
 if (c->leftChild() || c->rightChild())
 _pendingNodes.push(c);
 if (_tree->rightChild()) _current = IO_right;
 else if (_pendingNodes.size() > 0)
 {
 _tree = _pendingNodes.front();
 _pendingNodes.pop();
 _current = _tree->leftChild() ? 
 IO_left : IO_right;
 } 
 else _tree = 0;
 return current();
 case IO_right:
 c = (BinaryTreeNodePtr) current();
 if (c->leftChild() || c->rightChild()) 
 _pendingNodes.push(c);
 if (_pendingNodes.size() > 0)
 {
 _tree = _pendingNodes.front();
 _pendingNodes.pop();
 _current = _tree->leftChild() ?
 IO_left : IO_right;
 } 
 else _tree = 0; 
 return current();
 }
}

void
BFTreeNodeIterator::reset()
{
 _tree = _origTree;
 _current = IO_node;
}

I'm not going to give a detailed explanation of these member functions. The basic behavior is that the root node is returned via current() and then its left and right children are returned when next() is called. As each child is returned it is added to a queue. Once all children of a node have been returned the node at the head of the queue is retrieved and then that node's children are returned and queued. The result is that each "level" of the tree is visited and all nodes below that level are enqueued to be revisited.

Implementing The TreeBuilder Class

The TreeBuilder class hierarchy implements the Builder design pattern. It encapsulates the building of a composite data structure and hides the details of how that structure is composed by defining an AddNode() member function to add nodes to the tree. The base class defines this interface.

// Builder for making trees - base class
class TreeBuilder {
public:
 virtual void AddNode(TreeNodePtr theNode) {}
 virtual TreeNodePtr GetTree() { return 0; }
protected:
 TreeBuilder() {};
};

New nodes are added to the current tree being built by allocating them and passing them to the TreeBuilder AddNode() member function. The completed tree is returned by the GetTree() member function. The BinaryTreeBuilder subclass implements a simple binary tree. No effort is made to balance the tree.

// Builder for making binary trees
class BinaryTreeBuilder : public TreeBuilder {
public:
 BinaryTreeBuilder();
 virtual void AddNode(TreeNodePtr theNode);
 virtual TreeNodePtr GetTree();
private:
 TreeNodePtr _currentBTree;
};

// - BinaryTreeBuilder member fns -
BinaryTreeBuilder::BinaryTreeBuilder() 
{
 _currentBTree = 0;
}

TreeNodePtr
BinaryTreeBuilder::GetTree() 
{
 return _currentBTree;
}

void
BinaryTreeBuilder::AddNode(TreeNode* theNode) 
{
 BinaryTreeNodePtr testNode = 
 (BinaryTreeNodePtr) _currentBTree;
 if (!testNode) _currentBTree = theNode;
 else
 {
 for (;;)
 {
 if (((BinaryTreeNodePtr)theNode)
 ->compare(*testNode) <0)
 {
 if (testNode->leftChild()) 
 testNode = testNode->leftChild();
 else 
 {
 testNode->setLeftChild(
 (BinaryTreeNodePtr)theNode);
 return;
 }
 }
 else 
 {
 if (testNode->rightChild()) 
 testNode = testNode->rightChild();
 else 
 {
 testNode->setRightChild(
 (BinaryTreeNodePtr)theNode);
 return;
 }
 }
 }
 }
}

The AddNode() member function traverses the current tree, comparing the new node to the current node by using the TreeNode compare() member function. Once the correct location is located (i.e., a leaf node is reached) the new node is added as a left or right child of the leaf node.

The HBTreeBuilder subclass builds height-balanced binary trees. To implement a height-balanced binary tree we restructure the tree as we add new nodes.

// Builder for making height-balanced binary trees
class HBTreeBuilder : public TreeBuilder {
public:
 HBTreeBuilder();
 virtual void AddNode(TreeNodePtr theNode);
 virtual TreeNodePtr GetTree();
private:
 TreeNodePtr _currentBTree;
};

// - HBTreeBuilder member fns -
HBTreeBuilder::HBTreeBuilder() 
{
 _currentBTree = 0;
}

TreeNodePtr
HBTreeBuilder::GetTree() 
{
 return _currentBTree;
}

void
HBTreeBuilder::AddNode(TreeNode* theNode) 
{
 BinaryTreeNodePtr testNode = 
 (BinaryTreeNodePtr) _currentBTree;
 if (!testNode) _currentBTree = theNode;
 else
 {
 BinaryTreeNodePtr grandparent = 0, parent = 0;
 for (;;)
 {
 if (((BinaryTreeNodePtr)theNode)
 ->compare(*testNode) <0)
 {
 if (testNode->leftChild())
 {
 grandparent = parent;
 parent = testNode;
 testNode = testNode->leftChild();
 }
 else 
 {
    // balance a tree with a long left chain
 if (parent && grandparent &&
 !(parent->rightChild()) && 
 !(testNode->rightChild()))
 {
 if (parent->compare(*grandparent) <0)
 grandparent->setLeftChild(testNode);
 else 
 grandparent->setRightChild(testNode);
 parent->setLeftChild(0);
 testNode->setRightChild(parent);
 }
 testNode->setLeftChild(
 (BinaryTreeNodePtr)theNode);
 return;
 }
 }
 else 
 {
 if (testNode->rightChild())
 {
 grandparent = parent;
 parent = testNode;
 testNode = testNode->rightChild();
 }
 else 
 {
    // balance a tree with a long right chain
 if (parent && grandparent &&
 !(parent->leftChild()) && 
 !(testNode->leftChild()))
 {
 if (parent->compare(*grandparent) <0)
 grandparent->setLeftChild(testNode);
 else 
 grandparent->setRightChild(testNode);
 parent->setRightChild(0);
 testNode->setLeftChild(parent);
 }
 testNode->setRightChild(
 (BinaryTreeNodePtr)theNode);
 return;
 }
 }
 }
 }
}

As in the last class, the AddNode() member function traverses the current tree, comparing the new node to the current node by using the TreeNode compare() member function. This version keeps track of the previous two nodes visited as the tree is traversed. Once the correct location is found (that is, a leaf node is reached) the current and previous nodes are checked to see if both lack a child on the opposite side. If they do, then the subtree from the grandparent node (that is, two nodes above the leaf) is re-arranged such that it forms a balanced tree when a new node is added.

Implementing The Program

We implement a few functions, including main(), to put these classes to use. I'm using a function from the MoreFiles 1.4.3 library to traverse a Macintosh directory. The IterateDirectory() function accepts a directory, a function pointer that is a callback function you write, and a void * that can be whatever you'd like to supply to the callback. This is perfect for a Builder! All I do is pass a TreeBuilder to IterateDirectory(). It will be passed along to my callback, where I can use it to add the file or folder name to the current tree. Besides the main() and my callback function, I've written two functions that use tree node iterators to print the nodes in a tree.

#include "Patterns.h"
#include <iostream.h>
#include "IterateDirectory.h" 

// - ostream operators to print tree nodes -
ostream&
operator<< (ostream& os, const TreeNodePtr& t)
{
 os << *t; return os;
}
ostream&
operator<< (ostream& os, TreeNode& t)
{
 os << "[" << t.output() << "]\n";
 return os;
}

This pair of ostream operators use the output() member function of the TreeNode base class to print out a node in the tree. They can print out any subclass of TreeNode.

// - callback for IterateDirectory() -
pascal
void
getName(const CInfoPBRec * const cpbPtr,
 Boolean *quitFlag,
 void *tbuilder)
{
 char *cstr = p2cstr(cpbPtr->hFileInfo.ioNamePtr);
 ((TreeBuilder *)tbuilder)->
 AddNode(new BinaryTreeNode(cstr));
}

The getName() callback function is called for each name found by IterateDirectory(). The Pascal string version of the file name is converted into a C string. That string is used to allocate a BinaryTreeNode, which is passed to the TreeBuilder. Recall that the TreeBuilder instance was passed into this function by IterateDirectory() - we'll see where in a moment.

// - walk a tree using the TreeNodeIterator
void walkTree1(TreeNodeIterator &treeNodes)
{
 cout << 
 "* Fetching nodes via current/next operators\n";
 
 for ( ;treeNodes.current(); treeNodes.next())
 cout << treeNodes.current();
}
// - walk a tree using the TreeNodeIterator
void walkTree2(TreeNodeIterator &treeNodes)
{
 cout << "* Fetching nodes via ++ operator\n";

 while (TreeNodePtr p = treeNodes++)
 cout << p;

}

The walkTree1() and walkTree2() functions take a TreeNodeIterator and use it to access the nodes of a tree. They both produce the same output. They just use the two alternative APIs of TreeNodeIterator. I personally prefer the ++ operator version used in walkTree2() because it looks more like C++.

// - main program 
void main(void)
{
 cout << "Building binary tree\n";

 BinaryTreeBuilder b;

    // iterate over a directory named Example Dir
    // within the program's current working directory
 StringPtr dir = c2pstr(":Example Dir");

 IterateDirectory(0,0,dir,2,getName, &b);
 TreeNodePtr myTree = 
 (BinaryTreeNodePtr) b.GetTree();
 
 cout << 
 "The nodes, using a depth-first iterator:\n";

 DFTreeNodeIterator depthFirst(
 *(BinaryTreeNodePtr)myTree);
 walkTree1(depthFirst);
 
 cout << 
 "\nThe nodes, same iterator, alternate API:\n";

 depthFirst.reset();
 walkTree2(depthFirst);
 
 cout << 
 "\nThe nodes, using a breadth-first "
 << "iterator:\n";

 BFTreeNodeIterator bredthFirst(
 *(BinaryTreeNodePtr)myTree);
 walkTree2(bredthFirst);

 cout << 
 "\nBuilding height balanced binary tree\n";

 HBTreeBuilder hb;

 IterateDirectory(0,0,dir,2, getName, &hb);
 myTree = (BinaryTreeNodePtr) hb.GetTree();

 cout << 
 "The HB nodes, using a depth-first iterator:\n";
 
 DFTreeNodeIterator depthFirstHB(
 *(BinaryTreeNodePtr)myTree);
 walkTree2(depthFirstHB);

 cout << 
 "\nThe HB nodes, using a BF iterator:\n";

 BFTreeNodeIterator bredthFirstHB(
 *(BinaryTreeNodePtr)myTree);
 walkTree2(bredthFirstHB);
 
 cout << "\nDone\n";
}

The main() function first builds a binary tree version of the example directory by allocating a BinaryTreeBuilder object and passing that object to IterateDirectory(), along with a pointer to the callback function getName(). IterateDirectory() calls getName() with each matching file or directory, and supplies the BinaryTreeBuilder object. Once all the entries are processed the completed tree is extracted by calling the GetTree() member function of the BinaryTreeBuilder object. The tree is used in the constructor for a DFTreeNodeIterator and the resulting object is passed to each of the walkTree functions. Notice that we reset the iterator before passing it to the second walkTree function.

The whole process is repeated a second time. The only difference is that we create a HBTreeBuilder object to build a height-balanced binary tree. The example directory and program output are shown in Figure 1.

Figure 1. Program Output.

Building binary tree
The nodes, using a depth-first:
* Fetching nodes via current/next operators
[Folder 1]
[Folder 2]
[Folder 3]
[FolderHelp]
[Fri]
[Jaki]
[Jan]
[Mar]

The nodes, same iterator, alternate API:
* Fetching nodes via ++ operator
[Folder 1]
[Folder 2]
[Folder 3]
[FolderHelp]
[Fri]
[Jaki]
[Jan]
[Mar]

The nodes, using a breadth-first iterator:
* Fetching nodes via ++ operator
[Folder 1]
[Fri]
[Folder 2]
[Jaki]
[Folder 3]
[Jan]
[FolderHelp]
[Mar]

Building height balanced binary tree
The HB nodes, using a depth-first iterator:
* Fetching nodes via ++ operator
[Folder 1]
[Folder 2]
[Folder 3]
[FolderHelp]
[Fri]
[Jaki]
[Jan]
[Mar]

The HB nodes, using a BF iterator:
* Fetching nodes via ++ operator
[Folder 1]
[Fri]
[Folder 3]
[Jan]
[Folder 2]
[FolderHelp]
[Jaki]
[Mar]

Done

Note that although both the binary and height-balanced trees produce the same output when accessed via the depth-first, they produce different output with a breadth-first. That's because the two trees are organized differently.

Design Patterns, Made Easy

I use the Standard Template Library stack, queue, and list objects in the class implementations. The STL (with documentation) is provided with recent versions of CodeWarrior. I mention this because the STL implements several important design patterns as templated classes, and is itself very dependent on design patterns. The stack and queue class templates are examples of the Adapter design pattern. They add new interfaces (such as the stack push() and pop() and the queue top() methods) to an existing object (the list) to create new objects. You can learn quite a bit about design patterns simply by reading about the STL.

Tao, Revisited

Applying design patterns to a design makes major impacts to both the design and the implementation. Let me point out exactly what those impacts are in the example program.

We used a TreeBuilder class (an example of the Builder creational pattern) to construct our tree of file names. That moved the code (and algorithm, since we used subclasses) out of our main program and into a class. Our main program is simpler, and can build many different kinds of trees. We also gain a reusable TreeBuilder class just in case we ever want to build these or similar types of trees again.

We used the TreeNodeIterator class (an example of the behavioral pattern) to retrieve the nodes from the tree. This allows us to move the implementation (and algorithm) for accessing tree nodes from the application into the class. We could use a TreeNodeIterator to access each node of the tree and do whatever we want to the node. For example we might search the tree for a particular file name using the iterator.

We used STL stacks and queues (examples of the Adapter structural pattern) within our TreeNodeIterator subclasses. This allowed us to quickly and easily extend an existing class (the STL list) to provide new behavior specific to a stack and queue. Adapters are an excellent way to reuse trusted and debugged code. They simply add new behavior in the form of new member functions to an existing class.

Is this just good object oriented programming? Sort of. Good object oriented designers would almost certainly create a TreeNode class hierarchy. Since we applied design patterns we ended up with a main program that is structured a little differently than the obvious non-pattern approach to design. We also encapsulated the building and accessing algorithms in class hierarchies. This makes the classes smarter, and frees the main program from the details of the tree organization. We get more general (and therefore more reusable) classes and a simpler main program.

I hope I've been able to interest you in the who area of design patterns. This is not just an academic area of study. You can make real improvements to your designs and gain real productivity advances in maintaining and reusing code. All you have to do is look at the problem with patterns in mind.

Bibliography and References

E. Gamma, R. Helm, R. Johnson, and J. Vlissides, Design Patterns. Addison-Wesley, ISBN 0-201-63361-2, 1995

Luther, Jim. "MoreFiles 1.4.3". Available via FTP at

ftp://ftpdev.info.apple.com/Developer_Services/Sample_Code/MoreFiles_1.4.3/.

Rensselaer Polytechnic Institute, "The Standard Template Library". A web site with a general description of STL and links to web sites containing documentation.

http://www.cs.rpi.edu/~musser/stl.html.

 
AAPL
$117.60
Apple Inc.
-1.03
MSFT
$47.47
Microsoft Corpora
-0.12
GOOG
$541.08
Google Inc.
+1.81

MacTech Search:
Community Search:

Software Updates via MacUpdate

MacUpdate Desktop 6.0.3 - Discover and i...
MacUpdate Desktop 6 brings seamless 1-click installs and version updates to your Mac. With a free MacUpdate account and MacUpdate Desktop 6, Mac users can now install almost any Mac app on macupdate.... Read more
SteerMouse 4.2.2 - Powerful third-party...
SteerMouse is an advanced driver for USB and Bluetooth mice. It also supports Apple Mighty Mouse very well. SteerMouse can assign various functions to buttons that Apple's software does not allow,... Read more
iMazing 1.1 - Complete iOS device manage...
iMazing (was DiskAid) is the ultimate iOS device manager with capabilities far beyond what iTunes offers. With iMazing and your iOS device (iPhone, iPad, or iPod), you can: Copy music to and from... Read more
PopChar X 7.0 - Floating window shows av...
PopChar X helps you get the most out of your font collection. With its crystal-clear interface, PopChar X provides a frustration-free way to access any font's special characters. Expanded... Read more
Carbon Copy Cloner 4.0.3 - Easy-to-use b...
Carbon Copy Cloner backups are better than ordinary backups. Suppose the unthinkable happens while you're under deadline to finish a project: your Mac is unresponsive and all you hear is an ominous,... Read more
ForeverSave 2.1.3 - Universal auto-save...
ForeverSave auto-saves all documents you're working on while simultaneously doing backup versioning in the background. Lost data can be quickly restored at any time. Losing data, caused by... Read more
Voila 3.8.1 - Capture, annotate, organiz...
Voila is a screen-capture, recording, and annotation tool that is a full-featured replacement for Mac's screen-capture and screen-recording capabilities. It has a large and robust set of editing,... Read more
SyncTwoFolders 2.0.6 - Syncs two user-sp...
SyncTwoFolders simply synchronizes two folders. It supports synchronization across mounted network drives and it is a possibility to run a simulation showing in a log what will be done. Please visit... Read more
Duplicate Annihilator 5.1.1 - Find and d...
Duplicate Annihilator takes on the time-consuming task of comparing the images in your iPhoto library using effective algorithms to make sure that no duplicate escapes. Duplicate Annihilator detects... Read more
HandBrake 0.10.0 - Versatile video encod...
HandBrake is a tool for converting video from nearly any format to a selection of modern, widely supported codecs. Supported Sources: VIDEO_TS folder, DVD image or real DVD (unencrypted -- CSS is... Read more

Latest Forum Discussions

See All

Tilt to Live Bundle Set to Arrive This T...
Tilt to Live Bundle Set to Arrive This Thanksgiving Posted by Ellis Spice on November 25th, 2014 [ permalink ] One Man Left has unveiled an upcoming Tilt to Live bundle, allowing players to get the series for a di | Read more »
BattleLore: Command (Entertainment)
BattleLore: Command 1.0 Device: iOS Universal Category: Entertainment Price: $9.99, Version: 1.0 (iTunes) Description: ***NOTE: Compatible with iPad 2/iPad mini, iPod touch 5 and up and iPhone 4S and up – WILL NOT RUN ON EARLIER... | Read more »
Weather Or Not Review
Weather Or Not Review By Jennifer Allen on November 25th, 2014 Our Rating: :: STYLISH WEATHER REPORTINGiPhone App - Designed for the iPhone, compatible with the iPad Check the weather quickly and conveniently with Weather or Not... | Read more »
The All-New Football Manager Handheld 20...
The All-New Football Manager Handheld 2015 is Available Now Posted by Jessica Fisher on November 25th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Six iOS Games to Get You Ready for Thank...
Image Source: Friends Wiki At this point in the month, you or at least a few people you know are probably getting ready to scramble around (or are already scrambling around) for Thanksgiving Dinner. It’s a hectic day of precise oven utilization, but... | Read more »
Call of Duty: Heroes: Tips, Tricks, and...
Hello Heroes: What’d we think of Call of Duty‘s take on Clash of Clans? Check out our Call of Duty: Heroes review to find out! Just downloaded Call of Duty: Heroes and need some handy tips and tricks on how to get ahead of the rest? As we often do,... | Read more »
Call of Duty: Heroes Review
Call of Duty: Heroes Review By Jennifer Allen on November 25th, 2014 Our Rating: :: CLASH OF FRANCHISESUniversal App - Designed for iPhone and iPad Mix Clash of Clans with Call of Duty, and this is what you get.   | Read more »
Slider Review
Slider Review By Jordan Minor on November 25th, 2014 Our Rating: :: SLIDE TO PLAYUniversal App - Designed for iPhone and iPad Slider has all the excitement of unlocking your phone screen.   | Read more »
oh my giraffe (Games)
oh my giraffe 1.0.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0.0 (iTunes) Description: Eat fruits while being chased by lions. Cut the vines to send fruit plummeting onto the lions. Don't worry, your flexible... | Read more »
One of 2000’s Most Loves Adventure Games...
One of 2000’s Most Loves Adventure Games, The Longest Journey, has Come to iOS Posted by Jessica Fisher on November 25th, 2014 [ permalink ] | Read more »

Price Scanner via MacPrices.net

Early Black Friday MacBook Pro sale: 15-inch...
 Best Buy has posted early Black Friday prices on 15″ Retina MacBook Pros, with models on sale for $300 off MSRP on their online store for a limited time. Choose free local store pickup (if available... Read more
A9 Chips Already?
It’s barely more than a couple of months since Apple got the first A8 systems-on-chip into consumer hands, but rumor and news focus is already turning to the next-generation A9 SoC. Apple Daily... Read more
NewerTech Announces NuGuard KXs Impact X-Orbi...
NewerTech has announced updates to its family of Impact X-Orbing Screen Armor bringing military grade, triple layer protection to Apple’s new iPhone 6 and 6 Plus. Like all models in the NuGuard KXs... Read more
13-inch 1.4GHz MacBook Air on sale for $889,...
 B&H Photo has the 13″ 1.4GHz/128GB MacBook Air on sale for $889 including free shipping plus NY tax only. Their price is $110 off MSRP. B&H will also include free copies of Parallels Desktop... Read more
Save up to $300 on Macs and iPads with your A...
Purchase a new Mac or iPad at The Apple Store for Education and take up to $300 off MSRP. All teachers, students, and staff of any educational institution qualify for the discount. Shipping is free,... Read more
Apple refurbished Mac Pros available for up t...
The Apple Store is offering Apple Certified Refurbished Mac Pros for up to $600 off the cost of new models. An Apple one-year warranty is included with each Mac Pro, and shipping is free. The... Read more
Jumptuit Launches One-Tap Windows 8.1 iTunes...
Jumptuit has launched Windows 8.1 support for One-Tap iTunes Sync. with which Windows 8.1 users can now easily sync their iTunes libraries with Microsoft OneDrive. Jumptuit provides easy access from... Read more
Apple restocks refurbished 13-inch 2014 Retin...
The Apple Store has restocked Apple Certified Refurbished 2014 13″ 2.6GHz Retina MacBook Pros for up to $230 off the cost of new models. An Apple one-year warranty is included with each model, and... Read more
CEA Study Finds More People Recycling Electro...
A new study by the Consumer Electronics Association (CEA) finds that electronics recycling receives the continued and growing support of consumers. According to the CEA,s Recycling and Reuse Study,... Read more
15″ 2.2GHz Retina MacBook Pro on sale for $17...
 B&H Photo has the 2014 15″ 2.2GHz Retina MacBook Pro on sale today for $1749. Shipping is free, and B&H charges NY sales tax only. B&H will also include free copies of Parallels Desktop... Read more

Jobs Board

*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
*Apple* Solutions Consultant (ASC)- Retail S...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
Project Manager, *Apple* Financial Services...
**Job Summary** Apple Financial Services (AFS) offers consumers, businesses and educational institutions ways to finance Apple purchases. We work with national and Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.