我正在尝试使用以下简单模型在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将提供添加,删除等模块和课程所需的所有标准功能。
另一种可能的方法是:
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原则?将孩子当作财产会更好吗?
答案 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
的功能,那么这是继承它的不同但也是有效的理由。