I have a quite complicated graph-like data structure. For the sake of clarity, let's simplify it to:
class Node;
class AbstractEdge { void foo() {} };
class Edge1: public AbstractEdge { void bar1() {} };
class Edge2: public AbstractEdge { void bar2() {} };
As you can see, our graph is not like any other graph: there exists two kinds of edge, both inheriting from AbstractEdge. This design cannot be changed. Now, assume that I have to design two classes along the lines of:
class OrientedEdge1
{
Edge1 * edge;
bool orientation;
void foo() { edge->foo(); }
void bar1() { edge->bar1(); }
}
class OrientedEdge2
{
Edge2 * edge;
bool orientation;
void foo() { edge->foo(); }
void bar2() { edge->bar2(); }
}
In practice, OrientedEdge1::foo() and OrientedEdge2::foo() are much longer than just calling a single method, but the idea is that they are identical, calling only methods inherited from AbstractEdge.
What design would you use to factorize code? I am thinking of three approaches:
foo_impl(AbstractEdge * edge) { edge->foo(); }
class OrientedEdge1
{
Edge1 * edge;
bool orientation;
void foo() { foo_impl(edge); }
void bar1() { edge->bar1(); }
}
class OrientedEdge2
{
Edge2 * edge;
bool orientation;
void foo() { foo_impl(edge); }
void bar2() { edge->bar2(); }
}
Pros:
Cons:
Not all methods can be implemented as free functions.
The declaration code is still duplicated.
class AbstractOrientedEdge
{
AbstractEdge * edge;
bool orientation;
void foo() { edge->foo(); }
}
class OrientedEdge1: public AbstractOrientedEdge
{
Egde1 * edge1() { return static_cast<Egde1*>(edge); }
void bar1() { edge1()->bar1(); }
}
class OrientedEdge2: public AbstractOrientedEdge
{
Egde2 * edge2() { return static_cast<Egde2*>(edge); }
void bar2() { edge2()->bar2(); }
}
Pros:
More factorization.
I do not plan to use these two classes polymorphically, but who knows, maybe the fact that they are related by inheritance might turn useful in the future.
Cons:
Need care in constructors/setters to enforce that OrientedEdge1::edge always points to a Egde1*.
Somehow, the static_cast feels wrong.
template <class EdgeT>
class OrientedEdge
{
EdgeT * edge;
bool orientation;
void foo() { edge->foo(); }
}
class OrientedEdge1: public OrientedEdge<Edge1>
{
void bar1() { edge->bar1(); }
}
class OrientedEdge2: public OrientedEdge<Edge2>
{
void bar2() { edge->bar2(); }
}
Pros:
Most factorization.
The pointer stored in both classes has the correct type, no casting needed.
Cons:
Questions: Which approach would you tend to use? Do you have any other solutions or suggestions?
1 and 3.
3 removes duplicate boilerplate, 1 moves implementation wherever I want.