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:
- Linux: opendir / readdir / closedir
- Windows: FindFirstFile / FindNextFile / FindClose
// 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:- Directory structure (as seen)
- Windows hierarchies
- Menu structures
- etc.
Written and © by P. Most ()