PERA Software Solutions GmbH

The "Guide" pattern

The "Guide" pattern

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: // EntryVisitor.hpp 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: void acceptVisitor(EntryVisitor *visitor) override { visitor->onVisitFileEntry(*this); } }; class DirectoryEntry : public Entry { public: void acceptVisitor(EntryVisitor *visitor) override { 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 library like Qt with its QDirIterator and so I use it for the examples. Now if you want a list with the names of the directory entries you would do it like this: // QueryDirectoryEntries.cpp 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 Qt it looks like this: // DirectoryGuide.cpp 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) { 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: // DirectoryEntriesCounter.cpp class DirectoryEntriesCounter : public DirectoryVisitor { public: void onVisitDirectory(const directory_entry &) override { ++entriesCount_; } void onVisitFile(const directory_entry &) override { ++entriesCount_; } unsigned long entriesCount() const { return entriesCount_; } private: unsigned long entriesCount_ = 0; }; int main(int, char *[]) { DirectoryEntriesCounter entriesCounter; DirectoryGuide guide; guide.walk("/tmp/", &entriesCounter); cout << entriesCounter.entriesCount() << endl; return 0; } or if you just want to query the names you would do it like this: // DirectoryEntriesQuery.cpp class DirectoryEntriesQuery : public DirectoryVisitor { public: void onVisitDirectory(const directory_entry &entry) override { entries_.push_back(entry.path().string()); } void onVisitFile( const directory_entry &entry ) override { 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; }

Conclusion

The Guide pattern can be used whenever there is an object structure for which functions are provided to iterate through the objects like: 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 other widgets like radio-, check- or push-buttons etc. and with the help of the Guide pattern this was much easier.