构建 C++用于可维护性和封装的类层次结构

发布于 2024-11-28 17:17:01 字数 1610 浏览 2 评论 0原文

我有一些关于封装的一般性问题,因为它与可维护性有关。这是我用来帮助构建解析树的示例类。 (出于教育目的,我避免使用 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 使用它与 的友谊来处理任何修改节点。这真的比拥有变异器并结束与 ParseTree 的友谊更好吗?如果我添加变异器,那么 Node 就可以在其他上下文中重用,而无需添加其他友谊。

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

  5. 如果我的方法还有其他任何我没有想到的明显和错误的地方,我很乐意听到。

I have some general questions about encapsulation as it relates to maintainability. Here is an example class that I used to assist in the construction of a parse tree. (I have avoided STL for education's sake.)

The Node class describes a node in a tree. The managing class ParseTree (not shown) is responsible for building and maintaining the collection of Node objects in a meaningful, tree-like way.

// 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_;
};

The following four topics relate to encapsulation.

  1. The static methods in the Node class are declared static because they can be phrased without using any private members. I'm wondering if they should live outside Node in a common namespace, or maybe as static members within ParseTree. Should my decision be dominated by the fact that ParseTree is responsible for trees, and by that logic the functions should live in ParseTree?

  2. On a related note, the reason the static methods are in Node instead of ParseTree was because ParseTree was filling up with lots of members. I've read that keeping class small and agile is better for maintainability. Should I be going out of my way to find methods that don't rely on private member access and pull them out of my class definition and put them into functions grouped within the same namespace as the class?

  3. I had also read some advice about avoiding mutators on private members since it tends to break encapsulation, so I ended up only having accessors, and let ParseTree handle any modifications using its friendship with Node. Is this really better than having mutators and just ending the friendship with ParseTree? If I add mutators, then Node can be reused in other contexts without adding another friendship.

  4. If I add mutators and remove the static functions from Node, I feel like I could just make the data members public and remove all the accessors/mutators/friend declarations. I have the impression that such an approach would be bad form. Should I be skeptical of my design if I have accessor/mutator pairs for each private member?

  5. If there's anything else obvious and wrong about my approach that I didn't think to ask, I'd appreciate hearing about it.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

折戟 2024-12-05 17:17:01

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

说实话,我建议你首先让你的程序逻辑正确,然后你就可以担心 OO 的花哨的事情了。
当您继续进行时,您提出的问题可能会自行回答。

Ask yourself, what's a Node? Clearly, it's something that may have a parent, a left child and a right child. It also holds a pointer to some data. Does a node have a height? It depends on the context, is it possible that your nodes may at some point be part of a cycle? A ParseTree has a concept of height, it doesn't seem a node does.

To be honest, I suggest you get your program logic correct first, and then you can worry about the OO bells and whistles.
The questions you're asking will probably answer themselves as you proceed.

只是我以为 2024-12-05 17:17:01

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

namespace mycompiler {
    class Node {
        ...
    };

    class ParseTree {
        ...
    };

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

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

I think Node is a bit too crowded with these accessors, which are apparently just an indirect way of exposing your private members. I think removing these static members to an application namespace would be a bit cleaner. Eg:

namespace mycompiler {
    class Node {
        ...
    };

    class ParseTree {
        ...
    };

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

In that way you could still avoid polluting the global namespace, but at the same time keeping your Node and ParseTree classes smaller. You could also overload some mycompiler:: functions (e.g. Print()) to accept any object from your namespace if you don't want to stick them into your classes. This would make Node and ParseTree more intelligent containers, while some external logic (to the relevant classes) could be isolated in mycompiler::.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文