C++中的依赖反转原则

360 阅读4分钟

编写可维护、可重用和可扩展的代码

在用C++这样的高级语言设计代码结构时,考虑到代码的可维护性和可重用性等因素是非常重要的。如今,软件开发人员不断受到新需求、平台和不断变化的库版本的轰炸。一个设计良好的软件应该能够处理这些问题。设计(和维护)不好的软件倾向于到处都是依赖性的意大利面条代码。

开发人员经常依靠设计模式和设计原则来帮助他们设计软件。设计模式是针对常见情况的经过试验和验证的配方。另一方面,设计原则是良好系统设计的指导方针。虽然设计模式通常适用于一种特定的语言或至少是一种范式(如面向对象的编程),但原则往往更为普遍。

依赖反转原则(DIP)是SOLID的设计原则之一。它指出。

高级模块不应该依赖于低级模块。两者都应该依赖于抽象。抽象不应该依赖于细节。细节应该依赖于抽象。

高级模块通常指的是你代码中更抽象和复杂的操作 。这些操作需要一个或多个低级模块 来执行其功能。因此,实现这种复杂功能的直接方法是使高层模块依赖于低层模块。

直截了当的实现

让我们来看看一个直接实现的例子,在一个假设的RPG游戏中使用两个类,即播放器。所需的功能是在玩家与门交互时打开或关闭门(取决于它的状态)。 正如你所看到的,它有一个相关的方法,interactiveWith。这个方法调用门类toggleOpen方法。现在让我们来看看门类

正如你所看到的,游戏中的门会在关闭和打开的状态之间进行切换。

实施中的问题

乍看之下,一切都很好。玩家能够与游戏中的门进行互动。然而,可扩展性应该一直在我们心中。在我们的实现中,由于玩家类对门类 的依赖,我们减少了代码的可扩展性和可重用性。如果玩家想与游戏中的其他对象进行交互呢?我们将需要为每个新的对象写一个单独的方法。

尊重依赖性反转原则的实现

这个问题的关键是引入一个InteractiveObject接口,让Door类可以实现。一个接口引入了一个抽象的概念,它将帮助我们把玩家和门解耦。这样一来,我们将坚持高层模块和低层模块都应该只依赖抽象的原则。

这个接口声明了允许与对象进行交互的方法。我们还稍微重构了Door类以实现这个接口。

播放器 类现在只需要知道InteractiveObject 接口,而不会关心对象的实现。这坚持了高层模块也应该只依赖抽象的原则。

你可以想象一下,通过抽象来实现模块的通信,就像驾驶汽车一样:你只需要知道如何打开引擎,如何转向,如何让汽车开得更快或更慢。你不需要知道发动机和变速器的复杂情况。

替代实施

为了说明依赖关系反转原则并不限于编写接口,让我们用模板来解决同样的问题。

你看,在这种情况下,我们不需要明确地声明InteractiveObject 接口,而是依靠Door 类实现的特定契约(在这种情况下,它需要实现interactive 方法。如果这个契约没有实现,编译器会抛出一个错误。由于模板方法没有使用动态多态性,它的运行速度可能会快上一丁点。否则,这两个实现几乎是相同的。

总结

正如你所看到的,依赖反转原则通过使高层模块依赖抽象而不是详细的低层实现来提高我们代码的可维护性、可重用性和可扩展性。