Odds and ends: the Visitor pattern
Greetings.
This week, the last design pattern covered in Implementing QuantLib: the Visitor pattern. Also, a couple of news items. The first is that this past Monday I have released QuantLib 1.11; you can go and download it—after you’re done with this post, hopefully. Some release notes are available, as well as the list of issues and pull requests for this version.
Second bit of news: Hacktoberfest 2017 is underway, and it’s your occasion to contribute to QuantLib (or any other open-source project on GitHub) and get a t-shirt for your effort. It looks like a good deal to me; then again, as you can see, I’m partial to t-shirts myself.
Thanks, @DigitalOcean! #hacktoberfest pic.twitter.com/ETjWCAEd5l
— Luigi Ballabio ن (@lballabio) December 13, 2016
Follow me on Twitter or LinkedIn if you want to be notified of new posts, or subscribe via RSS: the buttons for that are in the footer. Also, I’m available for training, both online and (when possible) on-site: visit my Training page for more information.
The Visitor pattern
Our implementation, shown in the next listing, follows the Acyclic
Visitor pattern [1] rather than the one in the Gang of Four book [2]:
we defined a degenerate AcyclicVisitor
class, to be used in the
interfaces, and a class template Visitor
which defines the pure
virtual visit
method for its template argument.
class AcyclicVisitor {
public:
virtual ~AcyclicVisitor() {}
};
template <class T>
class Visitor {
public:
virtual ~Visitor() {}
virtual void visit(T&) = 0;
};
The pattern also needs support from any class hierarchy that we want to be visitable; an example is shown in the listing that follows.
void Event::accept(AcyclicVisitor& v) {
Visitor<Event>* v1 = dynamic_cast<Visitor<Event>*>(&v);
if (v1 != 0)
v1->visit(*this);
else
QL_FAIL("not an event visitor");
}
void CashFlow::accept(AcyclicVisitor& v) {
Visitor<CashFlow>* v1 =
dynamic_cast<Visitor<CashFlow>*>(&v);
if (v1 != 0)
v1->visit(*this);
else
Event::accept(v);
}
void Coupon::accept(AcyclicVisitor& v) {
Visitor<Coupon>* v1 = dynamic_cast<Visitor<Coupon>*>(&v);
if (v1 != 0)
v1->visit(*this);
else
CashFlow::accept(v);
}
Each of the classes in the hierarchy (or at least, those that we want
to be specifically visitable) need to define an accept
method that
takes a reference to AcyclicVisitor
. Each of the methods tries to
cast the passed visitor to the specific Visitor
instantiation for
the corresponding class. A successful cast means that the visitor
defines a visit
method taking this specific class, so we invoke it.
A failed cast means that we have to look for an fallback. If the
class is not the root of the hierarchy (like CashFlow
or Coupon
in
the listing) we can call the base-class implementation of accept
,
which in turn will try the cast. If we’re at the root, like the
Event
class, we have no further fallback and we raise an
exception. (Another alternative would be to do nothing, but we
preferred not to fail silently.)
Finally, a visitor is implemented as the BPSCalculator
class shown
in this post. It inherits
from AcyclicVisitor
, so that it can be passed to the various
accept
methods, as well as from an instantiation of the Visitor
template for each class for which it will provide a visit
method.
It will be passed to the accept
method of some instance, which will
eventually call one of the visit
methods or raise an exception.
I already discussed the usefulness of the Visitor pattern in the post I mentioned above, so I refer you to it (the unsurprising summary: it depends). Therefore, I will only spend a couple of words on why we chose Acyclic Visitor.
In short, the Gang-of-Four version of Visitor might be a bit faster,
but it’s a lot more intrusive; in particular, every time you add a new
class to the visitable hierarchy you’re also forced to go and add the
corresponding visit
method to each existing visitor (where by
“forced” I mean that your code wouldn’t compile if you didn’t). With
Acyclic Visitor, you don’t need to do it; the accept
method in your
new class will fail the cast and fallback to its base class. (In fact,
you’re not even required to define an accept
method; you could just
inherit it. However, this would prevent visitors to target this
specific class.) Mind you, this is convenient but not necessarily a
good thing (like a lot of things in life, I might add): you should
review existing visitors anyway, check whether the fallback
implementation makes sense for your class, and add a specific one if
it doesn’t. But I think that the disadvantage of not having the
compiler warn you is more than balanced by the advantage of not having
to write a visit
method for each cash-flow class when, as in
BPSCalculator
, a couple will suffice.
Bibliography
[1] R.C. Martin, Acyclic Visitor. In Pattern Languages of Program Design 3. Addison-Wesley, 1997.
[2] E. Gamma, R. Helm, R. Johnson and J. Vlissides, Design Patterns: Element of Reusable Object-Oriented Software. Addison-Wesley, 1995.