S
Published on
· Last modified on
· Public

c++ public继承引发的面向对象相关思考(原创)

c++ public继承引发的面向对象相关思考

问题

binary-search-tree构思时,我已经决定要在已经成熟的tree库基础上进行拓展,特化,实现二叉查找树。再此之前,我已经物色好,选定tree.hh作为基础,我喜欢stl-like的泛化数据结构,出于对c++的尊重。所以,需要考虑用何种方式拓展tree.hh。

二进制查找树(bst)在传统认知中,属于普通树(tree),即bst "is a" tree。这没什么可争议的,不止如此,红黑树、B树、都应该是普通树的一种 -- is a关系。根据面向对象的常识,is a应使用public继承:

class bst_tree:public tree;

OK,就这样愉快的决定了。

待我进一步思考,发现了不妥。bst的性质是,node->左子树< node < node->右子树

tree::append_child(iterator iter, const T& x);

这个方法在一个节点下插入一个子节点。在tree中没有任何问题。让我们来展示一下用法

tree<int> t;
tree::iterator iter_root = t.insert(tr.begin(), 10);
t.append_child(iter_root, 11);
assert((*iter_root.begin()) == 11);

以上代码在tree类中没有任何问题。但如果在bst中呢?改如何实现append_child方法?

  • append_child等一系列方法,执行“插入”操作后自动调整bst,满足bst的性质

    如果这样实现,显然无法满足上面对于tree的用法。根据里氏替换原则,通俗说法,只要父类能出现的地方必须能够用子类替换,且不会产生任何错误和异常。 显然,这样实现不妥

  • append_child延续tree的实现方式。 好吧,这没什么可考虑的,破坏了bst的性质,已经不能叫bst了。

bst是tree的一种,是is a的关系,那为什么在用面向对象方式设计bst时,遇到了如此严重的矛盾呢??

每一个程序员都是一名哲学家,我需要搞清楚这个问题。找到了《Effective c++》条款32 public继承,看到了与我的问题相同的例子。 关于矩形和正方形,矩形::只增加宽(新的宽)。 这个方法会破坏正方形的性质。那么既然大师已经提到此问题,我们该如何解决呢?

结论

让我来精简一下核心矛盾。 bst是tree的一种 -> is-a关系应该用public继承 -> public继承应符合里氏替换原则 -> 符合里氏替换原则后bst和tree会有矛盾

大师的解释是,问题出在第一个环节bst是tree的一种。传统观念中得“直觉”并不完全适用于软件设计。

如上面所说的,每一个程序员都是一名哲学家。 好吧,经过大师的点播,我也有些自己的感悟:

其实问题还是出在“传统观念中”,我们再举一个与程序无关的例子。 人 、 好人。 人是有做坏事的行为能力的(方法),当人做了坏事后,就不是好人了。 在传统观念中,先存在实体,我们人类再将各种各样的标签动态的赋予到已经存在的实体中。所以一个好人,是可以变成坏人的在程序设计中,我们要先设计各种各样的标签(类),根据标签生成实体, 已经存在的实体,是无法修改自己的标签的 。 面向对象的方式,是为了更方便的将代码复用、易于拓展、易于维护,终归只是一种方法,终归无法和世界观完全等同。 其实之前在网上有看到不少黑面向对象的名猿们,现在也算是理解一点点了。

最终来说一下我自己的解决方式。再此之前总结一下class之间的关系类型:

  • is-a (是一类): public继承
  • has-a (包含): 组合
  • is-implemented-in-terms-of (根据某物实现出) : private继承、组合

我的bst属于第三种 is-implemented-in-terms-of ,那么应该用组合还是继承呢。原则是: - 需要访问protected成员 - 需要重写virtual方法。

有以上两条需求,使用private继承,否则用组合实现(因为大师说过,能用组合就用组合)。

Sign in or Sign up Leave Comment