The "Guide" pattern
Written by P. Most   

How can you apply the visitor pattern if you don't control the object structure or if there isn't one to begin with.

Content:

Motivation

Implementation

Advanced Implementation

Conclusion




Motivation


According to the GoF book the intent of the Visitor pattern is:

"Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates."

 What it doesn't mention is that you must be able to modify the object structure so that you can add the 'acceptVisitor' method. So for example if you want to operate on a directory structure you would need something like this:

class EntryVisitor {
	public:
		virtual void onVisitFileEntry( const FileEntry &fileEntry );
		virtual void onVisitDirectoryEntry( const DirectoryEntry &directoryEntry );
};

class Entry {
	public:
		virtual void acceptVisitor( EntryVisitor *visitor ) = 0;
};

class FileEntry : public Entry {
	public:
		virtual void acceptVisitor( EntryVisitor *visitor )
		{
			visitor->onVisitFileEntry( *this );
		}
};

class DirectoryEntry : public Entry {
	public:
		virtual void acceptVisitor( EntryVisitor *visitor )
		{
			visitor->onVisitDirectoryEntry( *this );
		}
};


The problem is that the underlying operating system might use such a structure internally, but it's not accessible to the developer. Even if it were you are certainly not able to modify it. What you usually get instead are a couple of functions which lets you iterate through a directory:

But it's much easier to use a class library like Boost with its 'directory_iterator' or Qt with its 'QDirIterator', so I use these for the examples. Now if you want to count how many entries a directory contains you would do it with Boost like this:

unsigned long countDirectoryEntries( const string &directoryPath )
{
	unsigned long entriesCount = 0;
	
	for_each( directory_iterator( directoryPath ), directory_iterator(), [&]( const directory_entry & ) {
		++entriesCount;
	});
	return entriesCount;
}

and with Qt you do it like this:

unsigned long countDirectoryEntries( const QString &directoryPath )
{
	unsigned long entriesCount = 0;
	
	QDirIterator directoryIterator( directoryPath );
	while ( directoryIterator.hasNext() ) {
		++entriesCount;
		directoryIterator.next();
	}
	return entriesCount;
}

If you want to query the directory entries you would do it with Boost like this:

list< string > queryDirectoryEntries( const string &directoryPath )
{
	list< string > entries;
	
	for_each( directory_iterator( directoryPath ), directory_iterator(), [&]( const directory_entry &entry ) {
		entries.push_back( entry.path().string() );
	});
	return entries;
}

and with Qt you do it like this:

QList< QString > queryDirectoryEntries( const QString &directoryPath )
{
	QList< QString > entries;
	
	QDirIterator directoryIterator( directoryPath );
	while ( directoryIterator.hasNext() ) {
		entries.append( directoryIterator.next() );
	}
	return entries;
}

But now you are facing the same problem that the Visitor pattern tries to solve: Each time you want to do something differently with the directory structure you are repeating the same boiler plate code for iterating through the entries.




Implementation


So to prevent the copying and pasting, you encapsulate the knowledge of how to iterate through a directory structure in a separate class and the first name which comes to mind when you think about a visitor is a Guide which takes the Visitor on a walk. With Boost this looks like this:

class DirectoryVisitor {
	public:
		virtual void onVisitDirectory( const boost::filesystem::directory_entry &entry ) = 0;
		virtual void onVisitFile( const boost::filesystem::directory_entry &entry ) = 0;
};

class DirectoryGuide {
	public:
		void walk( const boost::filesystem::path &directoryPath, DirectoryVisitor *visitor );
};

void DirectoryGuide::walk( const path &directoryPath, DirectoryVisitor* visitor )
{
	for_each( directory_iterator( directoryPath ), directory_iterator(), [=]( const directory_entry &entry ) {
		if ( is_directory( entry.path() ))
			visitor->onVisitDirectory( entry );
		else if ( is_regular_file( entry.path() ))
			visitor->onVisitFile( entry );
	});
}

and with Qt it looks like this:

class DirectoryVisitor {
	public:
		virtual void onVisitDirectory( const QFileInfo &fileInfo ) = 0;
		virtual void onVisitFile( const QFileInfo &fileInfo ) = 0;
};

class DirectoryGuide {
	public:
		void walk( const QString &directoryPath, DirectoryVisitor *visitor );
};

void DirectoryGuide::walk( const QString &directoryPath, DirectoryVisitor *visitor )
{
	QDirIterator dirIterator( directoryPath );
	while ( dirIterator.hasNext() ) {
		dirIterator.next();
		if ( dirIterator.fileInfo().isDir() )
			visitor->onVisitDirectory( dirIterator.fileInfo() );
		else if ( dirIterator.fileInfo().isFile() )
			visitor->onVisitFile( dirIterator.fileInfo() );
	}
}

Now if you again want to count how many entries a directory has you would do it like this:

class DirectoryEntriesCounter : public DirectoryVisitor {
	public:
		DirectoryEntriesCounter()
		{
			_entriesCount = 0;
		}
		
		virtual void onVisitDirectory( const directory_entry & )
		{
			++_entriesCount;
		}
		
		virtual void onVisitFile( const directory_entry & )
		{
			++_entriesCount;
		}
		
		unsigned long entriesCount() const
		{
			return _entriesCount;
		}
		
	private:
		unsigned long _entriesCount;
};

int main( int, char *[] )
{
	DirectoryEntriesCounter entriesCounter;
	DirectoryGuide guide;
	
	guide.walk( "/tmp/", &entriesCounter );
	cout << entriesCounter.entriesCount() << endl;
	
	return 0;
}

and if you just want to query the names you would do it like this:

class DirectoryEntriesQuery : public DirectoryVisitor {
	public:
		DirectoryEntriesQuery()
		{
		}
		
		virtual void onVisitDirectory( const directory_entry &entry )
		{
			_entries.push_back( entry.path().string() );
		}
		
		virtual void onVisitFile( const directory_entry &entry )
		{
			_entries.push_back( entry.path().string() );
		}
		
		list< string > entries() const
		{
			return _entries;
		}
		
	private:
		list< string > _entries;
};

int main( int, char *[] )
{
	DirectoryEntriesQuery entriesQuery;
	DirectoryGuide guide;
	
	guide.walk( "/tmp/", &entriesQuery );
	list< string > entries = entriesQuery.entries();
	for_each( entries.begin(), entries.end(), []( const string &name ) {
		cout << name << endl;
	});
	
	return 0;
}






Advanced Implementation


These examples so far should illustrate fairly well how the Guide pattern works, but the advantage of the Guide pattern becomes more obvious if the structure or the traversal algorithm becomes more complicated like when we try to recursively iterate through the complete directory tree. So the following examples show how to do that.

This example shows how to recursively walk the directory tree with C# (Mono) and the Qt bindings (Qyoto):

public interface DirectoryVisitor
{
	bool onEnterDirectory( QFileInfo fileInfo );
	void onLeaveDirectory( QFileInfo fileInfo );
	
	void onVisitFile( QFileInfo fileInfo );
}

public class DirectoryGuide
{
	public void walk( string directoryPath, DirectoryVisitor visitor )
	{
		const QDir.Filter flags = QDir.Filter.Files | QDir.Filter.Dirs | QDir.Filter.NoDotAndDotDot;
			
		var dirIterator = new QDirIterator( directoryPath, ( uint )flags, 0 );
		while ( dirIterator.HasNext() ) {
			dirIterator.Next();
			if ( dirIterator.FileInfo().IsDir() ) {
				if ( visitor.onEnterDirectory( dirIterator.FileInfo() )) {
					walk( dirIterator.FileInfo().FilePath(), visitor );
					visitor.onLeaveDirectory( dirIterator.FileInfo() );
				}
			}
			else if ( dirIterator.FileInfo().IsFile() )
				visitor.onVisitFile( dirIterator.FileInfo() );
		}		
	}
}

And this example shows how to do it with the 'normal' C# /.Net approach:

public interface DirectoryVisitor
{
	bool onEnterDirectory( DirectoryInfo directoryInfo );
	void onLeaveDirectory( DirectoryInfo directoryInfo );
	
	void onVisitFile( FileInfo fileInfo );
}

public class DirectoryGuide
{
	public void walk( string startDirectoryPath, DirectoryVisitor visitor )
	{
		var startDirectoryInfo = new DirectoryInfo( startDirectoryPath );
		foreach ( var directoryInfo in startDirectoryInfo.GetDirectories() ) {
			if ( visitor.onEnterDirectory( directoryInfo )) {
				foreach ( var fileInfo in directoryInfo.GetFiles() )
					visitor.onVisitFile( fileInfo );
				
				walk( directoryInfo.FullName, visitor );
				visitor.onLeaveDirectory( directoryInfo );
			}		
		}
		foreach ( var fileInfo in startDirectoryInfo.GetFiles() )
			visitor.onVisitFile( fileInfo );		
	}
}




Conclusion


The Guide pattern can be used wherever there is an object structure for which functions are provided to iterate through the objects like:

  • Directory structure (as seen)
  • Windows hierarchies
  • Menu structures
  • etc.

In one of my projects I implemented a translation on the fly where I walked through the menu structure to query the menu text and checked whether a translation was available and if it were then I replaced the menu text with the translation. The same was done with widgets like radio-, check- or push-buttons etc. and with the help of the Guide pattern this was much easier.




According to the GoF book the intent of the Visitor pattern is:


„Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.“


What it doesn't mention is that you must be able to modify the object structure so that you can add the 'Accept' method. So if you want to operate on a directory structure you need something like this:





class Entry {
	public:
		virtual string name() const = 0;
};



class FileEntry : public Entry {
	public:
		virtual string name() const;
};



class DirectoryEntry : public Entry {
	public:
		virtual string name() const;
};




Last Updated on Tuesday, 06 September 2011 09:32
 

Add comment


Security code
Refresh

PERA Software Solutions GmbH, Powered by Joomla! and designed by SiteGround web hosting