Структурирование иерархий классов C ++ для удобства обслуживания и инкапсуляции - PullRequest
2 голосов
/ 11 августа 2011

У меня есть несколько общих вопросов по поводу инкапсуляции, поскольку она связана с ремонтопригодностью. Вот пример класса, который я использовал, чтобы помочь в построении дерева разбора. (Я избегал STL ради образования.)

Класс Node описывает узел в дереве. Управляющий класс ParseTree (не показан) отвечает за создание и поддержание коллекции Node объектов осмысленным древовидным способом.

// contents of node.h, not including header guard or namespace
class Token;
class Node {
public:
  static const Node* FindParent(const Node* p_root, const Node* p_node);
  static int Height(const Node* p_root);
  static void Print(const Node* p_root);
  Node(const Token * p_tok=0) : p_left_(0), p_right_(0), p_tok_(p_tok) {}
  ~Node() { delete p_left_; delete p_right_; }
  const Node* p_left(void) const { return p_left_; }
  const Node* p_right(void) const { return p_right_; }
  const Token* p_tok(void) const { return p_tok_; }
private:
  friend class ParseTree;
  Node* p_left_;
  Node* p_right_;
  Token* p_tok_;
};

Следующие четыре темы относятся к инкапсуляции.

  1. Статические методы в классе Node объявлены статическими, поскольку их можно формулировать без использования каких-либо закрытых членов. Мне интересно, должны ли они жить вне Node в общем пространстве имен или, может быть, как статические члены в ParseTree. Должно ли мое решение основываться на факте, что ParseTree отвечает за деревья, и по этой логике функции должны жить в ParseTree?

  2. В связанной заметке причина, по которой статические методы в Node вместо ParseTree, заключалась в том, что ParseTree заполнялось большим количеством членов. Я читал, что держать класс маленьким и гибким лучше для удобства обслуживания. Должен ли я изо всех сил пытаться найти методы, которые не полагаются на закрытый доступ к членам, и извлечь их из определения моего класса и поместить их в функции, сгруппированные в том же пространстве имен, что и класс?

  3. Я также прочитал несколько советов о том, как избегать мутаторов на закрытых членах, поскольку это имеет тенденцию нарушать инкапсуляцию, поэтому я в итоге получил только аксессоры и позволил ParseTree обрабатывать любые модификации, используя его дружбу с Node. Это действительно лучше, чем иметь мутаторов и просто прекратить дружбу с ParseTree? Если я добавлю мутаторы, то Node можно будет повторно использовать в других контекстах, не добавляя еще одну дружбу.

  4. Если я добавлю мутаторы и удалим статические функции из Node, я чувствую, что могу просто сделать элементы данных общедоступными и удалить все декларации доступа / мутаторов / друзей. У меня сложилось впечатление, что такой подход был бы плохим тоном. Должен ли я скептически относиться к своему дизайну, если у меня есть пары аксессоров / мутаторов для каждого частного участника?

  5. Если в моем подходе есть что-то еще очевидное и неправильное, о котором я даже не подумал спросить, я был бы рад услышать об этом.

Ответы [ 2 ]

1 голос
/ 11 августа 2011

Спросите себя, что такое узел?Понятно, что это то, что может иметь родителя, левого ребенка и правого ребенка.Он также содержит указатель на некоторые данные.Есть ли у узла высота?Это зависит от контекста, возможно ли, что ваши узлы в какой-то момент могут быть частью цикла?ParseTree имеет концепцию высоты, а не узел.Вопросы, которые вы задаете, вероятно, ответят сами по себе.

1 голос
/ 11 августа 2011

Я думаю, Узел слишком переполнен этими средствами доступа, которые, по-видимому, являются лишь косвенным способом разоблачения ваших частных пользователей.Я думаю, что удаление этих статических членов в пространство имен приложения будет немного чище.Например:

namespace mycompiler {
    class Node {
        ...
    };

    class ParseTree {
        ...
    };

    const Node* FindParent(...);
    int Height(...);
    void Print(...);
}

Таким образом, вы все равно можете избежать загрязнения глобального пространства имен, но в то же время уменьшите классы Node и ParseTree.Вы также можете перегрузить некоторые mycompiler:: функции (например, Print()), чтобы принять любой объект из вашего пространства имен, если вы не хотите вставлять их в свои классы.Это сделало бы Node и ParseTree более интеллектуальными контейнерами, в то время как некоторая внешняя логика (для соответствующих классов) могла бы быть изолирована в mycompiler::.

...