【问题标题】:Structuring C++ class hierarchies for maintainability and encapsulation构建 C++ 类层次结构以实现可维护性和封装性
【发布时间】:2011-10-24 11:03:13
【问题描述】:

我有一些关于封装的一般性问题,因为它与可维护性有关。这是我用来帮助构建解析树的示例类。 (为了教育,我避免使用 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. 我还阅读了一些关于避免在私有成员上使用 mutator 的建议,因为它往往会破坏封装,所以我最终只有访问器,并让 ParseTree 使用它与 Node 的友谊来处理任何修改。这真的比拥有变异器并结束与ParseTree 的友谊更好吗?如果我添加 mutators,那么 Node 可以在其他上下文中重用,而无需添加其他友谊。

  4. 如果我添加修改器并从Node 中删除静态函数,我觉得我可以将数据成员公开并删除所有访问器/修改器/朋友声明。我的印象是,这种方法是不好的形式。如果每个私有成员都有访问器/突变器对,我应该对我的设计持怀疑态度吗?

  5. 如果我的方法还有什么明显和错误的地方我不想问,我会很高兴听到的。

【问题讨论】:

  • 你的论点在兜圈子,当我想到这些问题时,我就会发生这种情况。我不会太担心它,你看起来你知道你在做什么。但是很快 1) 我认为它们不应该在 Node.js 中。 2) 是的,全局函数。 3)我认为重用Node没有什么优势,好像它没有任何价值,让它与ParseTree联系在一起。 4) 不,因为对于像 Node 这样简单的东西来说,所有公开都是可能的。
  • 'Mutator',天哪,Setter 和 Getter 有什么问题?
  • 过去几年我大部分时间都在做底层工作(IC 设计、hdl、嵌入式 C)。我的一个好朋友在向我描述面向对象设计时总是使用访问器和修改器,所以当我想到与私有成员交互的公共成员函数时,这就是我的想法。

标签: c++ data-structures coding-style encapsulation


【解决方案1】:

问问自己,什么是节点?显然,它可能有父母、左孩子和右孩子。它还包含指向某些数据的指针。节点有高度吗?这取决于上下文,您的节点是否有可能在某个时候成为循环的一部分? ParseTree 有一个高度的概念,它似乎不是一个节点。

说实话,我建议你先把你的程序逻辑弄对,然后你就可以担心 OO 的花里胡哨了。 您提出的问题可能会在您继续进行时自行回答。

【讨论】:

  • 谢谢,您提出的问题让我有了一条有用的思路。也就是说,我完成了我的计划,但我觉得我的组织不合适。而且我还没有很好的直觉或一组问题,我可以问自己来组织我的代码。此外,我还没有处理足够大的问题,以至于我的幼稚设计会损害我的实现。
【解决方案2】:

我认为 Node 有点过于拥挤这些访问器,这显然只是暴露您的私人成员的间接方式。我认为将这些静态成员删除到应用程序命名空间会更干净一些。例如:

namespace mycompiler {
    class Node {
        ...
    };

    class ParseTree {
        ...
    };

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

这样你仍然可以避免污染全局命名空间,但同时保持你的 Node 和 ParseTree 类更小。如果您不想将它们粘贴到您的类中,您还可以重载一些 mycompiler:: 函数(例如 Print())以接受来自命名空间的任何对象。这将使 Node 和 ParseTree 成为更智能的容器,而一些外部逻辑(到相关类)可以在 mycompiler:: 中隔离。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-01-23
    • 1970-01-01
    • 2014-01-30
    • 1970-01-01
    • 2015-05-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多