Search code examples
c++templatesinheritancecode-duplication

I have these two similar classes. What design would you use to factorize code?


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:

1. Using free functions

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:

  • Very simple solution, much better than not factorizing at all.

Cons:

  • Not all methods can be implemented as free functions.

  • The declaration code is still duplicated.

2. Using inheritance

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.

3. Using templates

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:

  • Need to keep implementation of shared code in the header, which force to include AbstractEdge.h (could be avoided with forward declarations with previous approaches).

Questions: Which approach would you tend to use? Do you have any other solutions or suggestions?


Solution

  • 1 and 3.

    3 removes duplicate boilerplate, 1 moves implementation wherever I want.