请求简单的 C++ 组合与继承示例
A Request for Simple C++ Composition vs. Inheritance Examples
我想了解 C++ 中组合和继承之间的语法差异。
我希望有人能提供两个简单的例子。一个使用组合的 class 示例和一个使用继承的 class 示例。
当然可以,为什么不呢?既然我喜欢机器人,那我们就做一个能走动能抓东西的机器人吧。我们将使用继承制作一个机器人,使用组合制作另一个机器人:
class Legs
{
public:
void WalkAround() {... code for walking around goes here...}
};
class Arms
{
public:
void GrabThings() {... code for grabbing things goes here...}
};
class InheritanceRobot : public Legs, public Arms
{
public:
// WalkAround() and GrabThings() methods are implicitly
// defined for this class since it inherited those
// methods from its two superclasses
};
class CompositionRobot
{
public:
void WalkAround() {legs.WalkAround();}
void GrabThings() {arms.GrabThings();}
private:
Legs legs;
Arms arms;
};
请注意,至少对于此示例,CompositionRobot
通常被认为是更好的方法,因为继承意味着 is-a
关系,而机器人不是特定类型的 Arms
并且机器人不是特定类型的 Legs
(而是机器人 has-arms
和 has-legs
)。
为了扩展@jeremy-friesner 的答案(并且主要重用他的代码),很多时候组合是使用比这更多的 classes 来实现的。本质上,Legs 和 Arms classes 将是接口的实现。这使得注入这些依赖项变得容易,因此,在对复合对象进行单元测试时 mock/stub 将它们移除。然后你会得到类似的东西(忽略虚拟析构函数......):
class Walker // interface
{
public:
virtual void Walk() = 0;
}
class Legs : public Walker
{
public:
void Walk() {... code for walking around goes here...}
}
class Grabber // Interface
{
public:
virtual void GrabThings() = 0;
}
class Arms : public Grabber
{
public:
void GrabThings() {... code for grabbing things goes here...}
}
class InheritanceRobot : public Legs, public Arms
{
public:
// Walk() and GrabThings() methods are implicitly
// defined for this class since it inherited those
// methods from its two superclasses
};
class CompositionRobot
{
public:
CompositionRobot(Walker& walker, Grabber& grabber)
: legs(walker),
arms(grabber)
{}
void Walk() {legs.Walk();}
void GrabThings() {arms.GrabThings();}
private:
Walker& legs;
Grabber& arms;
};
所以用于腿和手臂的实际实现可以设置在 运行 时间而不是编译时间。
顺便说一句,我只是把它写成一个答案,而不是对 Jeremy 的答案的评论,以便从代码格式中受益,所以如果你想投票,请也投票给 Jeremy。
HTH
2021 年 9 月 14 日更新:
我在这个答案中注意到的一件事是我将组合和聚合混为一谈。在组合中,如果父对象不复存在,那么子对象也不复存在,而在聚合中,子对象可能在父对象被销毁后存在。在我给出的描述中,在 CompositionRobot 构造函数中传递对子对象实例的引用意味着聚合关系而不是组合。但是,如果您在定义参数和创建对象时使用 std::unique_ptr(),并且在将它们存储在 CompositionRobot 的构造函数中时使用 std::move(),则效果与在 Jeremy 的回答中,对象(而不是指针或对它们的引用)被定义为 class 成员。
我想了解 C++ 中组合和继承之间的语法差异。
我希望有人能提供两个简单的例子。一个使用组合的 class 示例和一个使用继承的 class 示例。
当然可以,为什么不呢?既然我喜欢机器人,那我们就做一个能走动能抓东西的机器人吧。我们将使用继承制作一个机器人,使用组合制作另一个机器人:
class Legs
{
public:
void WalkAround() {... code for walking around goes here...}
};
class Arms
{
public:
void GrabThings() {... code for grabbing things goes here...}
};
class InheritanceRobot : public Legs, public Arms
{
public:
// WalkAround() and GrabThings() methods are implicitly
// defined for this class since it inherited those
// methods from its two superclasses
};
class CompositionRobot
{
public:
void WalkAround() {legs.WalkAround();}
void GrabThings() {arms.GrabThings();}
private:
Legs legs;
Arms arms;
};
请注意,至少对于此示例,CompositionRobot
通常被认为是更好的方法,因为继承意味着 is-a
关系,而机器人不是特定类型的 Arms
并且机器人不是特定类型的 Legs
(而是机器人 has-arms
和 has-legs
)。
为了扩展@jeremy-friesner 的答案(并且主要重用他的代码),很多时候组合是使用比这更多的 classes 来实现的。本质上,Legs 和 Arms classes 将是接口的实现。这使得注入这些依赖项变得容易,因此,在对复合对象进行单元测试时 mock/stub 将它们移除。然后你会得到类似的东西(忽略虚拟析构函数......):
class Walker // interface
{
public:
virtual void Walk() = 0;
}
class Legs : public Walker
{
public:
void Walk() {... code for walking around goes here...}
}
class Grabber // Interface
{
public:
virtual void GrabThings() = 0;
}
class Arms : public Grabber
{
public:
void GrabThings() {... code for grabbing things goes here...}
}
class InheritanceRobot : public Legs, public Arms
{
public:
// Walk() and GrabThings() methods are implicitly
// defined for this class since it inherited those
// methods from its two superclasses
};
class CompositionRobot
{
public:
CompositionRobot(Walker& walker, Grabber& grabber)
: legs(walker),
arms(grabber)
{}
void Walk() {legs.Walk();}
void GrabThings() {arms.GrabThings();}
private:
Walker& legs;
Grabber& arms;
};
所以用于腿和手臂的实际实现可以设置在 运行 时间而不是编译时间。
顺便说一句,我只是把它写成一个答案,而不是对 Jeremy 的答案的评论,以便从代码格式中受益,所以如果你想投票,请也投票给 Jeremy。
HTH
2021 年 9 月 14 日更新:
我在这个答案中注意到的一件事是我将组合和聚合混为一谈。在组合中,如果父对象不复存在,那么子对象也不复存在,而在聚合中,子对象可能在父对象被销毁后存在。在我给出的描述中,在 CompositionRobot 构造函数中传递对子对象实例的引用意味着聚合关系而不是组合。但是,如果您在定义参数和创建对象时使用 std::unique_ptr(),并且在将它们存储在 CompositionRobot 的构造函数中时使用 std::move(),则效果与在 Jeremy 的回答中,对象(而不是指针或对它们的引用)被定义为 class 成员。