我对封装有一些一般性问题,因为它与可维护性有关。这是我用来帮助构建解析树的示例类。 (为了教育,我避免使用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_;
};
以下四个主题与封装有关。
Node
类中的静态方法被声明为静态,因为它们可以在不使用任何私有成员的情况下进行措辞。我想知道他们是否应该在Node
之外的公共名称空间中生活,或者作为ParseTree
中的静态成员。我的决定应该由ParseTree
负责树木的事实来支配,并且按照这种逻辑,这些功能应该存在于ParseTree
中吗?
在相关的说明中,静态方法位于Node
而不是ParseTree
的原因是因为ParseTree
填满了大量成员。我已经读过,保持小而灵活的类对于可维护性更好。我是否应该不遗余力地找到不依赖于私有成员访问的方法,并将它们从我的类定义中拉出来,并将它们放入与类相同的命名空间中分组的函数中?
我还阅读了一些有关避免私有成员上的mutators的建议,因为它往往会破坏封装,所以我最终只有访问器,让ParseTree
使用{{1}的友谊来处理任何修改}}。这真的比变异人更好,只是结束与Node
的友谊吗?如果我添加mutators,那么ParseTree
可以在其他上下文中重用,而不会添加其他友谊。
如果我添加mutators并从Node
中删除静态函数,我觉得我可以公开数据成员并删除所有访问者/ mutators / friend声明。我的印象是这种方法形式不好。如果我为每个私人会员配备了访问者/变种对,我是否应该对我的设计持怀疑态度?
如果我的方法中有任何其他明显和错误的东西我没想到要问,我会很感激听到它。
答案 0 :(得分:1)
我认为 Node 对这些访问者来说太拥挤了,这显然只是暴露私人成员的间接方式。我认为将这些静态成员删除到应用程序命名空间会更清晰。例如:
namespace mycompiler {
class Node {
...
};
class ParseTree {
...
};
const Node* FindParent(...);
int Height(...);
void Print(...);
}
通过这种方式,您仍然可以避免污染全局命名空间,但同时保持Node和ParseTree类更小。您还可以重载某些mycompiler::
函数(例如Print()
)以接受命名空间中的任何对象,如果您不想将它们粘贴到您的类中。这将使Node和ParseTree成为更智能的容器,而某些外部逻辑(相关类)可以在mycompiler::
中隔离。
答案 1 :(得分:1)
问问自己,什么是节点?显然,这可能是一个父母,一个左孩子和一个正确的孩子。它还包含指向某些数据的指针。节点是否有高度?这取决于上下文,您的节点是否可能在某个时刻成为循环的一部分? ParseTree有一个高度概念,它似乎不是一个节点。
老实说,我建议你首先让你的程序逻辑正确,然后你可以担心OO的花里胡哨。 你提出的问题可能会在你继续进行时回答。