OGRE(面向对象图形渲染引擎)是一个面向场景、灵活的 3D 渲染引擎,用 C++ 编写,旨在让开发者更轻松、更直观地开发利用硬件加速 3D 图形的应用程序。该类库抽象了 Direct3D 和 OpenGL 等底层系统库的使用细节,并提供了基于世界对象和其他高级类的接口。
让我们用 CppDepend 来分析它,发掘它在设计上的优点。

OGRE3D 架构
依赖图展示了 OGRE3D 各项目之间的关系:

该架构是面向插件的,这对于在不修改内核项目“OgreMain”的情况下扩展 Ogre3D 非常有用。为了支持这种架构,OgreMain 项目提供了与插件通信所需的类。
让我们看看内核项目如何与插件通信——用以下查询搜索 RenderSystem_GL 插件所使用的 OgreMain 类:
SELECT TYPES WHERE IsDirectlyUsedBy "RenderSystem_GL"

该插件使用了一些工具类和模型实体,并重写了若干抽象类,以便将自己整合进 Ogre3D 生态系统。
让我们筛选下面的结果,只搜索 RenderSystem_GL 插件使用的抽象类:
SELECT TYPES WHERE IsDirectlyUsedBy "RenderSystem_GL" AND IsAbstract

插件可以重写场景、纹理、渲染行为等等。Ogre3D 提供了一套有趣的 API,可以轻松定制渲染,这让按我们的需求调整它变得非常容易。
继承与多态
使用面向对象的方法可能会导致为了充分利用多态而过度使用继承。
对于作为 Ogre3D 框架内核的 OgreMain 来说尤其如此——其中许多类就是被设计为由插件项目重写的。SELECT TYPES FROM PROJECTS "OgreMain" WHERE NbBaseClass >0

但多重继承会增加复杂性,我们必须谨慎使用。让我们搜索拥有多个基类的类。

只有少数类派生自多个类。
抽象度
对于面向插件的架构,宿主必须拥有大量抽象类,才能更加灵活、更具可扩展性。
让我们在 OgreMain 中搜索抽象类:SELECT TYPES FROM PROJECTS "OgreMain" WHERE IsAbstract

命名空间分层
CppDepend 提供了 DSM 图,我们可以对该矩阵进行三角化处理,聚焦标红的边框——它们会突出显示依赖环。

Ogre、Ogre::EmitterCommands 和 Ogre::OverlayElementCommands 之间存在一个依赖环。存在这样的依赖不一定是问题,但避免这类依赖有助于保持松耦合;这篇有趣的文章解释了分层的好处。
让我们搜索 Ogre 与另外两个命名空间之间依赖关系的来源。SELECT TYPES WHERE IsDirectlyUsedBy "Ogre"

Ogre 命名空间使用了另外两个命名空间中的所有 Cmd 类,而这些类都被 Ogre::ParticleEmitter 用作静态字段;ParticleEmitter 将它们添加到 CmdParam 字典中。
或许可以采用不同的实现方式来避免这个依赖环,尽管它并不是什么大问题。
类型内聚性
单一职责原则指出,一个类不应有超过一个变更理由。这样的类被称为内聚的。较高的 LCOM 值通常表明类的内聚性较差。LCOM 度量有好几种:LCOM 的取值范围是 [0-1];LCOMHS(HS 代表 Henderson-Sellers)的取值范围是 [0-2]。请注意,LCOMHS 度量通常被认为在检测非内聚类型方面更有效。LCOMHS 值高于 1 就应视为警讯。SELECT TYPES WHERE LCOMHS > 0.95 AND NbFields > 10 AND NbMethods >10 AND !IsGlobal

只有少数类被认为内聚性较低。
使用到的设计模式
单例模式
要确保一个类只有一个实例,最好的方式是使用单例模式。
让我们搜索作为单例的类:
SELECT TYPES WHERE DeriveFrom "Ogre.Singleton"

可以看到,几乎所有管理器类都是单例——一般来说,管理器是成为单例的绝佳候选。我们还可以搜索那些没有派生自 Singleton 的管理器类 SELECT TYPES WHERE !DeriveFrom "Ogre.Singleton" AND NameLike "Manager$"

只有少数管理器没有派生自 Singleton,这是正常的,因为那些类可以被多次实例化。
工厂模式
工厂模式对于抽象对象的创建过程非常有用;它能促进低耦合和高内聚,如这篇文章所述。

然而,拥有工厂并不保证实例一定通过它创建,因为类仍然可以被直接实例化。因此,我们需要定义一条规则来确保实例化经由工厂进行。
例如,对于 Entity 类,我们可以定义以下规则,找出每个实例化它的类:SELECT METHODS WHERE DepthOfCreateA "Ogre.Entity" == 1

可以看到,只有 EntityFactory 实例化了 Entity 类。
管理器模式
管理器类提供对各个子系统的访问,对于项目的模块化非常有用。Ogre3D 包含许多管理器,每一个都代表一个不同的子系统。
SELECT TYPES WHERE NameLike "Manager$"

外观模式
外观(facade)定义了一个让子系统更易使用的高层接口;我们可以使用传出耦合(Efferent Coupling)度量来检测项目中的外观。某个类型的传出耦合是它直接依赖的类型数量。TypeCe > 50 的类型依赖了过多的其他类型。它们很复杂,承担着不止一项职责,是重构的良好候选。
不过,对于外观类来说,较高的 TypeCe 是正常的。
让我们搜索 TypeCe 较高的类:SELECT TYPES ORDER BY TypeCe DESC

并非所有这些类都是外观,每个类的 TypeCe 偏高也许都有合理的解释。其中一些或许可以重构,但我们对 Ogre3D 的了解还不足以妄下结论。
使用最广泛的外观是 Root 类;让我们看看它使用了哪些子系统。

可以看到,Root 类几乎使用了所有管理器类。
我们还可以用以下 CQL 查询,搜索无法通过 Root 访问的管理器:
SELECT TYPES WHERE !IsDirectlyUsedBy "Ogre.Root" AND NameLike "Manager$" AND !IsAbstract

观察者模式
观察者(observer)是这样一种模式:一个称为主体的对象维护一份依赖者(称为观察者)列表,并在状态发生任何变化时自动通知它们——通常是调用观察者的某个方法。它主要用于实现事件处理系统。
Ogre3D 使用 Listener 类来实现观察者模式。
让我们在 OgreMain 项目中搜索 Listener 类。SELECT TYPES FROM PROJECTS "OgreMain" WHERE NameLike "Listener$"

结语
Ogre3D 是一个非常整洁、设计良好且文档完备的框架。您可以轻松理解它所使用的各种设计模式的意图,而它的模块化也能帮助您更快地掌握它的功能。
