The motivation
Let's say I'm writing a Tree
class. I will represent nodes of the tree by a Tree::Node
class. Methods of the class might return Tree::Node
objects and take them as arguments, such as a method which gets the parent of a node: Node getParent(Node)
.
I'll also want a SpecialTree
class. SpecialTree
should extend the interface of a Tree
and be usable anywhere a Tree
is.
Behind the scenes, Tree
and SpecialTree
might have totally different implementations. For example, I might use a library's GraphA
class to implement a Tree
, so that Tree::Node
is a thin wrapper or a typedef for a GraphA::Node
. On the other hand, SpecialTree
might be implemented in terms of a GraphB
object, and a Tree::Node
wraps a GraphB::Node
.
I'll later have functions which deal with trees, like a depth-first search function. This function should accept both Tree
and SpecialTree
objects interchangeably.
The pattern
I will use a templated interface class to define the interface for a tree and a special tree. The template argument will be the implementation class. For example:
template <typename Implementation>
class TreeInterface
{
public:
typedef typename Implementation::Node Node;
virtual Node addNode() = 0;
virtual Node getParent(Node) = 0;
};
class TreeImplementation
{
GraphA graph;
public:
typedef GraphA::Node Node;
Node addNode() { return graph.addNode(); }
Node getParent() { // ...return the parent... }
};
class Tree : public TreeInterface<TreeImplementation>
{
TreeImplementation* impl;
public:
Tree() : impl(new TreeImplementation);
~Tree() { delete impl; }
virtual Node addNode() { return impl->addNode(); }
virtual Node getParent() { return impl->getParent(); }
};
I could then derive SpecialTreeInterface
from TreeInterface
:
template <typename Implementation>
class SpecialTreeInterface : public TreeInterface<Implementation>
{
virtual void specialTreeFunction() = 0;
};
And define SpecialTree
and SpecialTreeImplementation
analogously to Tree
and TreeImplementation
.
My depth-first search function might look like this:
template <typename T>
void depthFirstSearch(TreeInterface<T>& tree);
and since SpecialTree
derives from TreeInterface
, this will work for Tree
objects and SpecialTree
objects.
Alternatives
An alternative is to rely more heavily on templates so that SpecialTree
isn't a descendent of TreeInterface
in the type hierarchy at all. In this case, my DFS function will look like template <typename T> depthFirstSearch(T& tree)
. This also throws out the rigidly defined interface describing exactly what methods a Tree
or its descendents should have. Since a SpecialTree
should always act like a Tree
, but provide some additional methods, I like the use of an interface.
Instead of the TreeInterface
template parameter being the implementation, I could make it take a "representation" class that defines what a Node
looks like (it will also have to define what an Arc
looks like, and so on). But since I'll potentially need one of these for each of the implementations, I think I'd like to keep this together with the implementation class itself.
What do I gain by using this pattern? Mostly, a looser coupling. If I'd like to change the implementation behind Tree
, SpecialTree
doesn't mind at all because it only inherits the interface.
The questions
So, does this pattern have a name? I'm using the handle-body pattern by storing a pointer to ContourTreeImplementation
in ContourTree
. But what about the approach of having a template-ized interface? Does this have a name?
Is there a better way to do this? It does seem that I am repeating myself a lot, and writing a lot of boilerplate code, but those nested Node
classes give me trouble. If Tree::Node
and SpecialTree::Node
had reasonably similar implementations, I could define a NodeInterface
interface for a Node
in TreeInterface
, and override the implementation of the node class in Tree
and SpecialTree
. But as it is, I can't guarantee that this is true. Tree::Node
may wrap a GraphA::Node
, and SpecialTree::Node
may wrap an integer. So this method won't quite work, but it seems like there might still be room for improvement. Any thoughts?