У меня довольно сложная графоподобная структура данных. Для ясности, давайте упростим это:
class Node;
class AbstractEdge { void foo() {} };
class Edge1: public AbstractEdge { void bar1() {} };
class Edge2: public AbstractEdge { void bar2() {} };
Как видите, наш график не похож на любой другой граф: существует два вида ребер, оба наследуются от AbstractEdge. Этот дизайн не может быть изменен. Теперь предположим, что мне нужно спроектировать два класса в соответствии с:
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(); }
}
На практике OrientedEdge1 :: foo () и OrientedEdge2 :: foo () намного длиннее, чем просто вызов одного метода, но идея в том, что они идентичны, вызывая только методы, унаследованные от AbstractEdge.
Какой дизайн вы бы использовали для факторизации кода? Я думаю о трех подходах:
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(); }
}
Плюсы:
Минусы:
Не все методы могут быть реализованы как свободные функции.
Код объявления все еще дублируется.
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(); }
}
Плюсы:
Больше факторизации.
Я не планирую использовать эти два класса полиморфно, но кто знает, может быть, тот факт, что они связаны наследованием, может оказаться полезным в будущем.
Минусы:
Нужна осторожность в конструкторах / установщиках, чтобы OrientedEdge1 :: edge всегда указывал на Egde1 *.
Почему-то static_cast чувствует себя не так.
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(); }
}
Плюсы:
Большая факторизация.
Указатель, хранящийся в обоих классах, имеет правильный тип, приведение не требуется.
Минусы:
Вопросы: Какой подход вы бы использовали? Есть ли у вас какие-либо другие решения или предложения?
1 и 3.
3 удаляет дубликаты шаблонов, 1 перемещает реализацию куда угодно.