简单场景的 C++ OO 继承正确性
C++ OO Inheritance Correctness for a simple scenario
我正在尝试使用以下简单模型在 C++ 中创建继承层次结构:课程、模块和课程,其中课程由零个或多个模块组成,模块由零个或多个课程组成。
这些 class 中的每一个还包含其他信息:
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;
...
};
我的问题:
- 如果有一个 class 所有这三个都继承自它是否正确,其中包含它们的共同属性?
就我个人而言,我认为他们以下列方式从 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,我被教导说 multiple-inheritance 是邪恶的,为了使用 Liskov Substitution Principle 等正确的 OO 继承,我要远离它。有很多不相信它的人和很多发誓的人。
另一种可能的方式是:
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 包含所有公共属性,这些私有向量包含它们的 children。
我想归结起来就是,课程真的 vector<module>
和模块真的 vector<lesson>
吗?这是否违反了 LSP 或其他 OO 原则?将他们的 children 设为 属性 会更好吗?
不建议从向量公开继承,因为课程、课程或模块不是向量。
正好用vector来实现,因为一个module可以有好几个course。如果明天您要使用集合、映射、链表或数据库映射器来实现您的对象,那么如果您基于向量继承,则必须重写整个代码。并且不要忘记,标准向量 emplace()、push_back()、erase()... 需要重载,因为模块中课程的所有这些操作都需要调整课程期间。
顺便说一下,class 设计的一个好的经验法则是对 "is-a" 关系使用继承,对 "has-a".
支持和反对向量继承的更多论点是in this SO question。
第二种选择看起来好多了,因为一门课程、一节课、一个模块实际上是一个命名实体。使用这种方法,对象之间的关系也更加清晰
这两种方法都可以与 Liskov 替换原则兼容:
- 如果您为
namedentity
编写代码,则可以使用相同的代码将命名实体替换为这些子class中的任何一个(前提是它们都实现了可以使用的构造函数参数与基 class 相同)。无论您将来对数据结构进行何种改进,这都是有意义的。
- 如果您编写用于处理
vector<module>
的代码,则相同的代码可以改为处理 course
。但是,您有可能在构造函数中使用不同的语义。如果你改变你的实现,LSP 会适得其反。
一般来说,从标准库容器继承是个坏主意。一个令人信服的原因是,在您描述的用例中,很难不提供派生 class 提供的额外功能,而不需要对容器上执行的操作进行一些限制,这会破坏可替代性。
相反,如果您认为 class 的 API 应该在子集合上公开容器操作,则提供一种方法来提供对容器的引用会更简洁、更容易, 存储为 class 的成员。更好的办法是只公开你需要的封闭容器的功能,这样你就有地方实施你的 class 现在或可能在不久的将来需要的任何不变量。
关于继承公共属性:通常这是一个好主意,但这样做有两个动机,即代码重用和抽象,它们不一定总是一致的。
从纯抽象 class 继承或多或少等同于在 Java 中实现一个接口,除了性能方面的考虑,没有理由避免从这样的 class 多重继承es 提供一点点照顾。另一方面,使用继承作为代码重用机制允许您使用本质上是组合的内容,而无需所有转发样板代码。
更具体地说:
我建议不要从 std::vector
继承。相反,提供在包含的 vector
上公开所需操作的方法。这使您既可以选择更改容器实现,又可以强加您可能需要的任何 class 不变量。
如果代码的任何其他部分只关心这些对象的命名,那么从 namedentity
继承是好的设计。
如果所有这些 classes 都需要一个非纯粹抽象的功能 namedentity
,那么这是一个不同但也是从它继承的正当理由。
我正在尝试使用以下简单模型在 C++ 中创建继承层次结构:课程、模块和课程,其中课程由零个或多个模块组成,模块由零个或多个课程组成。 这些 class 中的每一个还包含其他信息:
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;
...
};
我的问题:
- 如果有一个 class 所有这三个都继承自它是否正确,其中包含它们的共同属性?
就我个人而言,我认为他们以下列方式从 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,我被教导说 multiple-inheritance 是邪恶的,为了使用 Liskov Substitution Principle 等正确的 OO 继承,我要远离它。有很多不相信它的人和很多发誓的人。
另一种可能的方式是:
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 包含所有公共属性,这些私有向量包含它们的 children。
我想归结起来就是,课程真的 vector<module>
和模块真的 vector<lesson>
吗?这是否违反了 LSP 或其他 OO 原则?将他们的 children 设为 属性 会更好吗?
不建议从向量公开继承,因为课程、课程或模块不是向量。
正好用vector来实现,因为一个module可以有好几个course。如果明天您要使用集合、映射、链表或数据库映射器来实现您的对象,那么如果您基于向量继承,则必须重写整个代码。并且不要忘记,标准向量 emplace()、push_back()、erase()... 需要重载,因为模块中课程的所有这些操作都需要调整课程期间。
顺便说一下,class 设计的一个好的经验法则是对 "is-a" 关系使用继承,对 "has-a".
支持和反对向量继承的更多论点是in this SO question。
第二种选择看起来好多了,因为一门课程、一节课、一个模块实际上是一个命名实体。使用这种方法,对象之间的关系也更加清晰
这两种方法都可以与 Liskov 替换原则兼容:
- 如果您为
namedentity
编写代码,则可以使用相同的代码将命名实体替换为这些子class中的任何一个(前提是它们都实现了可以使用的构造函数参数与基 class 相同)。无论您将来对数据结构进行何种改进,这都是有意义的。 - 如果您编写用于处理
vector<module>
的代码,则相同的代码可以改为处理course
。但是,您有可能在构造函数中使用不同的语义。如果你改变你的实现,LSP 会适得其反。
一般来说,从标准库容器继承是个坏主意。一个令人信服的原因是,在您描述的用例中,很难不提供派生 class 提供的额外功能,而不需要对容器上执行的操作进行一些限制,这会破坏可替代性。
相反,如果您认为 class 的 API 应该在子集合上公开容器操作,则提供一种方法来提供对容器的引用会更简洁、更容易, 存储为 class 的成员。更好的办法是只公开你需要的封闭容器的功能,这样你就有地方实施你的 class 现在或可能在不久的将来需要的任何不变量。
关于继承公共属性:通常这是一个好主意,但这样做有两个动机,即代码重用和抽象,它们不一定总是一致的。
从纯抽象 class 继承或多或少等同于在 Java 中实现一个接口,除了性能方面的考虑,没有理由避免从这样的 class 多重继承es 提供一点点照顾。另一方面,使用继承作为代码重用机制允许您使用本质上是组合的内容,而无需所有转发样板代码。
更具体地说:
我建议不要从
std::vector
继承。相反,提供在包含的vector
上公开所需操作的方法。这使您既可以选择更改容器实现,又可以强加您可能需要的任何 class 不变量。如果代码的任何其他部分只关心这些对象的命名,那么从
namedentity
继承是好的设计。如果所有这些 classes 都需要一个非纯粹抽象的功能
namedentity
,那么这是一个不同但也是从它继承的正当理由。