(Примечание. Я ищу действительно любые предложения по правильным условиям поиска, чтобы прочитать об этой категории вопросов. "Object-relational-mapping" показался мне местом, где я мог бы найти хороший предшествующий уровень техники... но я не нашел ничего, что вполне соответствовало бы этому пока сценарий.)
У меня есть очень общий class Node
, который на данный момент можно представить себе как элемент в дереве DOM. Это не совсем то, что происходит — это граф объектов базы данных в файле с отображением памяти. Но аналогия довольно близка для всех практических целей, поэтому для простоты я буду придерживаться терминов DOM.
«Тег», встроенный в узел, подразумевает определенный набор операций, которые вы должны (в идеале) выполнять с ним. Сейчас я использую производные классы для этого. Так, например, если вы пытаетесь представить что-то вроде списка HTML:
<ul>
<li>Coffee</li>
<li>Tea</li>
<li>Milk</li>
</ul>
Базовое дерево будет состоять из семи узлов:
+--UL // Node #1
+--LI // Node #2
+--String(Coffee) // Node #3 (literal text)
+--LI // Node #4
+--String(Tea) // Node #5 (literal text)
+--LI // Node #6
+--String(Milk) // Node #7 (literal text)
Поскольку getString()
уже является примитивным методом на самих узлах, я бы, вероятно, сделал только class UnorderedListNode : public Node
, class ListItemNode : public Node
.
Продолжая эту гипотезу, давайте представим, что я хотел помочь программисту использовать менее общие функции, когда они знают больше о «типе»/теге Node, который у них есть. Возможно, я хочу помочь им со структурными идиомами в дереве, такими как добавление строкового элемента в неупорядоченный список или извлечение элементов в виде строки. (Это просто аналогия, так что не относитесь к процедурам слишком серьезно.)
class UnorderedListNode : public Node {
private:
// Any data members someone put here would be a mistake!
public:
static boost::optional<UnorderedListNode&> maybeCastFromNode(Node& node) {
if (node.tagString() == "ul") {
return reinterpret_cast<UnorderedListNode&>(node);
}
return boost::none;
}
// a const helper method
vector<string> getListAsStrings() const {
vector<string> result;
for (Node const* childNode : children()) {
result.push_back(childNode->children()[0]->getText());
}
return result;
}
// helper method requiring mutable object
void addStringToList(std::string listItemString) {
unique_ptr<Node> liNode (new Node (Tag ("LI"));
unique_ptr<Node> textNode (new Node (listItemString));
liNode->addChild(std::move(textNode));
addChild(std::move(liNode));
}
};
Добавление элементов данных в эти новые производные классы — плохая идея. Единственный способ действительно сохранить какую-либо информацию — это использовать базовые процедуры Node (например, вызов addChild
выше или getText
) для взаимодействия с деревом. Таким образом, реальная модель наследования — в той мере, в какой она существует — находится вне системы типов C++. То, что превращает узел <UL>
в "maybeCast" в UnorderedListNode
, не имеет ничего общего с vtables/и т.д.
Наследование C++ иногда кажется правильным, но обычно кажется неправильным. Я чувствую, что вместо наследования у меня должны быть классы, которые существуют независимо от Node, и просто каким-то образом взаимодействуют с ним как «помощники доступа»… но я не очень хорошо понимаю, на что это похоже.