我正在开发一个具有某些功能的类,我认为可能需要稍后扩展,但现在不能。如果要扩展类,那么我认为这会使实例化基类毫无意义。
例如,假设我的基类是树。一种方法是在树类中放置树需要做的所有事情,并将其留在那里。但是,这个树在以后的生活中可能对程序的其他方面很有用,所以我考虑创建一个纯虚拟onNodeVisited
函数。然后,派生类可以实现自己的onNodeVisited
版本,而不必担心基类中定义的树遍历的细节。
不使用纯虚函数并将树功能和特定于应用程序的功能保留在一个类(虚拟onNodeVisited
)中是否有意义?或者,我应该使树类抽象并为特定于应用程序的部分实现一个子类。
答案 0 :(得分:2)
我现在不会这样做。稍后你可以为Tree创建一个抽象基类,并在那里移动代码,如果这是有道理的。
继承此类事物的另一个选择是要调用的指向函数或函子类型。重用起来要容易得多,因为您不必为每种新情况继续创建新类。
答案 1 :(得分:1)
显然,这必须根据具体情况来决定,对于树的例子,似乎关键的决定/问题是树的所有用户是否需要/想要实现onNodeVisited函数。如果树可以同样地与接口的其他部分一起使用(例如,它支持get_next_child()
- 样式迭代,或者某些“路径”查找),那么它听起来像一棵树对于从未打算过的人有用访问每个节点,因此不希望实现onNodeVisited
函数。在这种情况下,onNodeVisited
不应该是纯虚拟的,现在或永远。如果你的设计决定是让树的界面如此受限制,以至于没有访问时类没用,那么你可能会坚持让人们通过使它成为纯虚拟来实现onNodeVisited
。
答案 2 :(得分:1)
我可以看到两个选择让我觉得合情合理。
如果您只是考虑可能最终会变得有用的事情,那么我会选择YAGNI路线,并将其完全保留,直到或除非您发现它真正需要它
如果您相当确定您确实需要它,并且没有足够的其他代码编写它来使用 那么即使你还没有使用它,也值得在课堂上进行设计。
这对不听起来像纯虚函数的好情况。纯虚函数意味着1)包含纯虚函数的类不能直接实例化 - 它只能用作基类,2)能够创建派生类的对象,他们必须提供纯虚函数的实现。
换句话说,纯虚函数表示几乎与您的情况相反。在类的对象完全存在之前,必须覆盖纯虚函数 。你(最多)建立一个“钩子”,使未来的扩展变得容易。
我将重复上面的建议:除非你完全确定将使用,否则最好只设计你做什么< / em>需要,并留在那。如果你做决定你需要(或者真的,真的想要)包含它,我会尽量保持松散耦合的合理性。显而易见的途径是使用访问者模式。这定义了一个处理访问和处理节点的单独访问者类。树节点类将包含一个额外的函数(传统上称为accept
),它接受一个参数:访问者对象的指针(或引用)。访问者类是一个典型的抽象基类,具有用于在节点上进行处理的成员函数(传统上名为visit
)。这通常是 一个抽象基类。
然后,当您决定在每个节点上确实要执行哪些处理时,从visitor
派生一个新类,它覆盖visit
以执行每个节点的处理。树根本不需要修改。这是众所周知的模式(即使在C ++程序员中,他们对其他人的模式通常不那么“友好”)所以假设你使用普通名称,大多数人会很快和容易地认出它。这也有助于代码本身通常非常简单 - accept
通常看起来像这样:
struct tree_node;
class visitor {
virtual void visit(tree_node *node) = 0;
};
struct tree_node {
void accept(visitor *v) { v->visit(this); }
// ...
};
然后要添加处理,您从访问者派生,并覆盖visit
以执行您需要的处理。树的一面根本不需要修改。这也非常精确,可以编写单个访问者类来访问多种类型的节点(即使节点不通过继承关联)。