Какой шаблон C++ использовать для библиотеки, которая позволяет расширять ее классы?
Я пытаюсь разделить некоторый код из моего программного обеспечения для моделирования C++ в библиотеку, чтобы его можно было использовать более гибко. Моделирование основано на Lattice
, состоящий из ряда Node
ы, которые содержат списки указателей на их Neighbor
s. Хотя соседи также являются узлами, я хотел бы иметь небольшой класс-обертку вокруг *Node
указатель для реализации дополнительной логики / полей (например, bool is_neares_neighbor
или так.
Моя классовая архитектура выглядит примерно так:
class Lattice {
private:
vector<Node> _nodes;
};
class Node {
private:
vector<Neighbor> _neighbors;
};
class Neighbor {
private:
Node * _node;
};
Все идет нормально. Теперь я хотел бы, чтобы моя библиотека обрабатывала всю логику, связанную с решеткой, но не более того. Тем не менее, при использовании библиотеки в каком-либо проекте, три класса (Lattice
, Node
, Neighbor
) будет нести больше логики и полей. Следовательно, пользователь должен иметь возможность наследовать от этих классов и реализовывать свои пользовательские вещи, в то время как библиотека все еще обрабатывает всю необходимую логику, связанную с решеткой.
Каков рекомендуемый способ сделать это? Подходят ли здесь шаблоны? В шаблонной ситуации моя иерархия классов будет выглядеть так:
template<class N>
class Lattice {
private:
vector<N> _nodes;
};
template<class NN>
class Node {
private:
vector<NN> _neighbors;
};
template<class N>
class Neighbor {
private:
N * _node;
};
Как видите, оба Node
а также Neighbor
Мне нужно знать тип друг друга, что является круговым условием. Я понятия не имею, как здесь иметь дело. Кроме того, вся библиотека должна была бы жить в заголовочных файлах.
Как такие ситуации решаются в мире C++ наиболее элегантным образом?
2 ответа
Я думаю, вы хотите, чтобы тип шаблона был "каким-то другим типом", который ваша библиотека решетки не знает или не заботится. Поэтому вместо того, чтобы пользователь наследовал от Node, вместо этого вы используете шаблон
Вы сами сказали, что Сосед - это Узел, поэтому вы должны иметь возможность определять все с точки зрения Узла
Вы также должны подумать, как вы собираетесь построить решетку, и как вы будете возвращать информацию о ней. Вот пример, где я предполагаю, что вы создаете решетку с помощью класса решетки, создающего узлы, с возможностью подключения их к существующим узлам во время создания.
#include <vector>
#include <memory>
template<class t_data>
class SetOfNodes;
template<class t_data>
class Node {
public:
Node(t_data value, SetOfNodes neighbors) : _data(value), _neighbors(neighbors) {}
is_nearest_neighbor(const Node &other){
// is other in _neighbors?
// is other the closest neighbor?
return false;
}
private:
t_data _data;
SetOfNodes _neighbors;
};
template<class t_data>
class SetOfNodes {
public:
std::vector<std::shared_ptr<Node<t_data>>> _nodes;
};
template<class t_data>
class Lattice {
public:
SetOfNodes get_all_nodes();
void create_new_node(SetOfNodes neighbors);
private:
SetOfNodes _nodes;
};
Я не совсем понимаю Neighbor, потому что это либо узел, либо он включает в себя два узла, поэтому я оставлю его в покое.
Я бы порекомендовал вам использовать комбинацию Composite / Visitor pattern:
- Composite поможет вам определить иерархию классов Lattice/Node/Neighbor/Custom_User_Node_Derived, которые можно обрабатывать единообразно (независимо от содержимого)
- Посетитель поможет вам просканировать иерархию и инкапсулировать логику для вашего класса Lattice / Node / Neighbor, а также позволит пользователю определить новые операции, которые вы пока не можете предсказать.
Это особенно рекомендуемый подход, когда вы хотите расширить библиотеку (или набор классов), над которыми у вас нет контроля. Насколько я понимаю, это то, что вы хотите, чтобы ваш пользователь мог.