Visitor pattern

Introduction

PmMda implements the visitor pattern to traverse a graph of data objects. This pattern allows to visit all data objects within a DOG (data object graph). Each data object is just visited once even if a data object is referenced more than once or if the DOG contains cycles.

The visitor is represented through the IDataObjectVisitor interface. The vistor specifies whether to visit unloaded items of paged collections, the current deep and whether a data object was already visited.
The DataObjectVisitorFunctor class is one default implementation of this visitor. This class allows to specify a functor that is executed for every visited data object. A functor must implement the IDataObjectFunctor interface which defines the Execute(IDataObject) method. This method is called once for every data object that is visited by the visitor. Even if the data object is referenced multiple times in the DOG, the Execute method is called only once. See the class diagram below for an overview of visitor and functor interfaces and classes.

Implementing a simple collect funtor

This exapmle implements a simple collect funtor that can collect all visited data object in a list.

public class MyCollectFunctor : IDataObjectFunctor
{
  private ArrayList m_collectedObjects;

  public MyCollectFunctor()
  {
    m_collectedObjects = new ArrayList();
  }

  public IDataObject[] CollectedObjects
  {
    get
    {
      return (IDataObject[]) m_collectedObjects.ToArray(typeof(IDataObject));
    }
  }

  public void Execute(IDataObject dataObject)
  {
    m_collectedObjects.Add(dataObject);
  }
}

Using the collect functor.

IDataObject myRootDataObject = Locator.DataObjectManager.DataObjectHandler.Retrieve(/*...*/);
MyCollectFunctor functor = new MyCollectFunctor();
DataObjectVisitorFunctor visitor = new DataObjectVisitorFunctor(functor,
                                                                true /*collect unloaded items too*/);
myRootDataObject.Accept(visitor);
IDataObject[] allObjectsReferencedByRoot = visitor.CollectedObjects;

Class diagram

Implementation

The implemented visitor is neighter a depth first nor a breadth first algorithm. It is important to visit the data objects in a fixed order to support NHibernate. The pmMDA framework uses the visitor pattern to collect all data objects within a DOG. The collected objects are stored using the NHibernate framework for which it is important that the row which is referenced by a foreign key is saved before the owner of the foreign key. Therefore the single properties must be saved before the indexed property.
To support the visitor pattern, every data object must implement the void Accept(IDataObjectVisitor visitor) method. Within that method the data object must first call the Accept method on all data objects that are referenced by a single property. Then the Visit method of the specified visitor is called (visitor.Visit(this)) before all data objects that are referenced by an indexed property are accepted.

Example:

public class MyDataObject : IDataObject
{
  // ...

  void Accept(IDataObjectVisitor visitor)
  {
    if (!visitor.Visited(this)
    {
      visitor.Depth++;
      // visit all single properties.
      m_property1.Accept(visitor);
      m_property2.Accept(visitor);
      //...

      // Visit the current data object
      visitor.Visit(this);

      // visit all items of the indexed properties.
      foreach (IDataObject obj in m_indexedProperty1)
      {
        obj.Accept(visitor);
      }
      foreach (IDataObject obj in m_indexedProperty2)
      {
        obj.Accept(visitor);
      }
      // ...
      visitor.Depth--;
    }
  }
}