Параллельные деревья наследования, где классы из одного дерева имеют контейнеры классов из другого - PullRequest
1 голос
/ 23 сентября 2010

Я действительно не знал, как указать проблему в заголовке, поэтому вот суть.

Я пишу классы графов Graph, Node и Edge, а затем делаю их на подклассы в VisGraph,VisNode и VisEdge для получения нарисованного графа (в C ++).Затем мне нужно дополнительно разделить их на конкретные классы, которые зависят от определенных данных.Так что у меня много параллельного наследования:

Graph -- VisGraph -- RouteGraph

Node  -- VisNode  -- RouteNode

Edge  -- VisEdge  -- RouteEdge

Это довольно уродливо, и я начал этим заниматься, так что я буду реализовывать функциональность постепенно, но есть много проблем.Например, один из них заключается в том, что базовый класс имеет контейнер всех экземпляров Node в графе.Проблема в том, что если я нахожусь в функции в VisGraph, имеющей дело с VisNode, которая требует функциональности, уникальной для VisNode, я должен сделать dynamic_cast для узлов, которые я получаю из контейнера в базовом классе.

Возможно я должен написать класс "Vis", который содержит Graph и рисует его?Я считаю, что наследование удобно, потому что каждый узел / ребро может легко нарисовать сам себя вместо того, чтобы хранить дополнительную информацию о позиции и т. Д. И рисовать их все по отдельности.

У вас есть какие-либо предложения / шаблоны проектирования, которые могли бы сделать это более элегантным?

Заранее спасибо.

1 Ответ

1 голос
/ 23 сентября 2010

Если сомневаетесь, бросайте шаблоны в проблему, пока она не сдастся:

template <typename N, typename E>
class Graph {
    std::vector<N> nodes;
    std::vector<E> edges;
};

typedef Graph<VisNode, VisEdge> VisGraph;
typedef Graph<RouteNode, RouteEdge> RouteGraph;

Вы потеряете наследование (RouteGraph больше не наследует от VisGraph), но это нормально в C ++ для типов контейнеров, а Graphчто-то вроде контейнера.Однако вы можете сохранить наследование между Node -> VisNode -> RouteNode.

Поскольку предполагается, что узлы и ребра имеют совпадающие типы, вы можете пойти еще дальше и дать Graph единственный параметр шаблона, который сам по себеявляется классом, содержащим типы ребер и узлов как typedefs.Но я не уверен, что оно того стоит.

Редактировать

Поскольку вы хотите последовательно добавлять функции, вы можете сохранить форму наследования, но потерять полиморфизм:

template <typename N, typename E>
class GraphImpl {
    std::vector<N> nodes;
    std::vector<E> edges;
};

template <typename N, typename E>
class VisGraphImpl : public GraphImpl<N, E> {
    // constructors

    // extra functions
};

template <typename N, typename E>
class RouteGraphImpl : public VisGraphImpl<N, E> {
    // constructors

    // extra functions
};

typedef GraphImpl<Node, Edge> Graph;
typedef VisGraphImpl<VisNode, VisEdge> VisGraph;
typedef RouteGraphImpl<RouteNode, RouteEdge> RouteGraph;

Может быть, есть и лучший способ, объединяя эти дополнительные функции в разумные миксины и используя CRTP:

template<typename Derived>
class VisFunctions {
    void somfunc() {
        myself = static_cast<Derived&>(*this);
        // do stuff
    }
};

Тогда:

class VisGraph : public Graph<VisNode, VisEdge>, public VisFunctions<VisGraph> {
    friend class VisFunctions<VisGraph>;
};

class RouteGraph : public Graph<RouteNode, RouteEdge>, public VisFunctions<RouteGraph>, public RouteFunctions<RouteGraph> {
    friend class VisFunctions<RouteGraph>;
    friend class RouteFunctions<RouteGraph>;
};

Не уверен, как это 'Я буду искать ваши реальные обстоятельства, хотя.Кстати, если вы не хотите / не нуждаетесь в объявлении друга для дополнительных функций, тогда вам не нужны эти дополнительные функции, чтобы вообще быть его членами - просто сделайте их свободными функциями, которые принимают параметр VisGraph или RouteGraph.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...