C ++ OO继承对于简单场景的正确性

时间:2015-03-12 00:02:40

标签: c++ oop inheritance

我正在尝试使用以下简单模型在C ++中创建继承层次结构:课程,模块和课程,其中课程由零个或多个模块组成,模块由零个或多个课程组成。 这些类中的每一个还包含其他信息:

class course
{
private:
    const std:string & name_;
    const std::duration duration_;
    const difficulty difficulty_;
    ...
};

class module
{
private:
    const std:string & name;
    const std::duration duration;
    ...
};

class lesson
{
private:
    const std:string & name;
    const std::duration duration;
    ...
};

我的问题:

  • 拥有一个所有这三个继承自的类,包含它们的公共属性是否正确?

就我个人而言,我认为以下列方式继承Vector会更为正确:

class course : public std::vector<module>
{
private:
    const std:string & name_;
    const std::duration duration_;
    const difficulty difficulty_;
    ...
};

class module : public std::vector<lesson>
{
private:
    const std:string & name;
    const std::duration duration;
    ...
};

class lesson
{
private:
    const std:string & name;
    const std::duration duration;
    ...
};

因为课程是模块的集合,而模块是具有一些附加属性的课程集合,继承自vector将提供添加,删除等模块和课程所需的所有标准功能。

  • 为什么不两个?我在学校学习继承的方式,使用Java,我被教导说多重继承是邪恶的,并且使用Liskov Substitution Principle等来保持适当的OO继承。有很多人不喜欢和#39相信它和很多其他人发誓。

另一种可能的方法是:

class course : public namedentity
{
private:
    std::vector<module> modules_;
    ...
    const difficulty difficulty_;
    ...
};

class module : public namedentity
{
private:
    std::vector<lesson> lesons_;
    ...
};

class lesson : public namedentity
{
private:
    ...
};

其中namedentity包含所有常见属性,并且这些私有向量包含其子项。

我想它归结为是,课程真的是vector<module>,模块真的是vector<lesson>吗?这是否违反LSP或其他OO原则?将孩子当作财产会更好吗?

2 个答案:

答案 0 :(得分:3)

不建议从向量中公开继承,因为课程,课程或模块不是向量。

恰好您使用向量来实现它们,因为模块可以有多个课程。如果您使用集合,地图或链接列表或数据库映射器来实现对象,那么如果您基于向量继承,则必须重写整个代码。并且不要忘记,标准向量emplace(),push_back(),erase(),...需要重载,因为模块中课程的所有这些操作都需要调整课程持续时间。

顺便说一句,对于类设计来说,一个好的经验法则是使用&#34; is-a&#34; 的继承关系和&#34;的组合-a&#34;

支持和反对向量继承的更多参数是in this SO question

第二种选择看起来好多了,因为课程,课程,模块确实是命名实体。使用这种方法,对象之间的关系也更加清晰

这两种方法都可以与利斯科夫订立原则兼容:

  • 如果您为namedentity编写代码,可以使用相同的代码替换任何这些子类的命名实体(前提是它们都实现了可以使用与基类相同的参数的构造函数)。无论您将来在数据结构上做出什么样的演变,这都是有道理的。
  • 如果您编写用于处理vector<module>的代码,则相同的代码可以使用course。但是,您可能会在构造函数中使用不同的语义。如果您更改实施,LSP将适得其反。

答案 1 :(得分:2)

作为一般规则,从标准库容器继承是一个坏主意。一个令人信服的理由是,在您描述的用例中,很难不提供派生类提供的额外功能,而不需要对容器上执行的操作进行某些限制,这会破坏可替代性。

相反,如果您认为您的类的API应该在子集合上公开容器操作,那么提供一个方法可以更清晰,更容易,该方法提供对容器的引用,存储为类的成员。更好的方法是仅公开所需的封闭容器的功能,以便您可以在现在或不久的将来强制执行您的类所需的任何不变量。

关于继承公共属性:通常这是一个好主意,但这样做有两个动机,即代码重用和抽象,并且它们不一定总是对齐。

继承纯粹的抽象类或多或少等同于在Java中实现接口,除了性能考虑之外,没有什么理由可以避免从这些类中进行多重继承,只需要小心。另一方面,使用继承作为代码重用的机制,允许您使用基本上没有所有转发样板代码的组合。

更具体:

  • 我建议不要继承std::vector。提供在所包含的vector上公开所需操作的方法。这允许您选择更改容器实现,并强制执行您可能需要的任何类不变量。

  • 如果代码的任何其他部分只关心这些对象的命名,那么从namedentity继承是很好的设计。

  • 如果所有这些类都需要非纯抽象namedentity的功能,那么这是继承它的不同但也是有效的理由。