C++继承vs组合:如何选择正确的设计模式 | 最佳实践

C++继承vs组合:如何选择正确的设计模式 #

引言 #

在C++面向对象设计中,继承和组合是两种最基本的代码重用机制。本文将通过具体实例,深入分析这两种方式的优缺点,帮助开发者在实际项目中做出更好的设计决策。

继承和组合,究竟我要选哪个? #

[每日一题]栏目有一道題,如图:

img

问题1:想让每个类对象都有一个唯一的 ID,有什么优雅的设计?

首先肯定要设计一个管理对象 ID 的类,即:

struct Object {    int64_t GetId();};

那如何将 Object 与其他类对象关联?可能有这两种方式:

///< 方式 1:继承
struct A : Object {};
struct B : Object {};

///< 方式 2:组合
struct A {    int64_t GetId() { return obj_.GetId(); }
   private:    Object obj_;};

继承和组合两种方式?选择哪种更好?

问题 2:音视频中,视频基本上包含音频,音频有的属性,视频也有。例如,音频可以设置音量,视频视频包含音频,那我如果也想设置音量,可能有两种方式。

组合方式如下:

struct Audio {    void SetVolume();};
struct Video : Audio {};
struct Video {    void SetVolume();
   private:    Audio audio_;};

继承方式如下:

struct Audio {    void SetVolume();};
struct Video : Audio {};

那应该怎么选择?

这两个问题其实可以归为一类问题,选择组合还是继承?

tips1: 这里说的视频不是音视频专业领域的视频轨,而是我们通常意义上的视频容易,既包含音频轨道也包含视频帧画面。

tips2: 也许大家说,这不显而易见吗,区分 has a 还是 is a,has a 就用组合方式,is a 就用继承方式。

上面的代码大家也看见了,遵循书本上的说法应该使用组合方式,可如果使用继承方式貌似会非常的方便。

如果 Audio 类有上百个方法,继承方式几行代码就能搞定,而组合方式,却需要重新在 Video 上暴露上百个方法,然后在实现中调用 Audio 的相关方法。(除非把 audio 变成 public 成员,变成 video.audio.SetVolume,这对用户调用貌似不太友好,用户想要的是直接设置视频音量。)

这貌似很麻烦,我们真的要严格的遵循 has a 组合、is a 继承的定式吗?

组合的缺点:

  • 更多的接口,更多的维护成本。

继承的缺点:

  • 层次过多后,代码可读性差、不好维护。

可能很多朋友都厌恶继承,特别是 C++ 中,很多朋友都把继承当作个洪水猛兽,不敢触碰,严格遵循多用组合少用继承的套路。

个人认为,没必要死扣字眼。固定编程范式。

多用组合、少用继承这种定式并不是绝对的,可以根据实际情况适当调整,控制好副作用,发挥各自的优势。

一般使用继承都是为了方便应对频繁变化的需求。但如果继承结构稳定,层次不多,最多两层,那可以大胆的尝试用继承,会更加方便。

回到上面的问题,即便它是 has a 的关系,但是因为使用组合方式带来的成本过高,而且它的继承层次只有一层,因此我倾向于使用继承方式。

至于 has a 还是 is a,可以改个名字再看一看:

struct Media {    void SetVolume();};
struct Audio : Media {};
struct Video : Media {};

嗯,这就是 is a 了。