零售游戏是否使用“控制反转”和“依赖注入”?


30

我知道许多更勤奋的软件开发人员正在转向控制反转依赖注入以处理对对象的引用。从Flash游戏的角度来看,我不知道AAA工作室的来龙去脉,所以:这些游戏是否用于零售游戏世界?


我认为,在此世界中,性能尤为重要,因为性能低下的用户将比程序员受到不良代码的冲击更大。
巴特·范·海克洛姆

依赖注入听起来像一个基于组件的系统,但是您是否能举一个例子说明控制反转的方式?
亚行

由于该线程的大部分似乎对IoC是什么以及它解决了什么有所误解,而OP并不是真正要求我首先向未来的访问者指出:sebaslab.com/ioc-container-unity-part-1
鲍里斯·卡伦斯

是的,他们有。实际上,Rare谈论了他们如何在“盗贼之海”中实施测试-youtube.com/watch?v=KmaGxprTUfI&t=1s。不幸的是,他们对虚幻引擎中的控制反转没有太多的话,但是,我编写了一个项目来演示其工作原理:github.com/jimmyt1988/UE-Tes​​ting
Jimmyt1988

Answers:


45

您称其为“勤奋的软件开发”,而我称其为“痛苦的过度设计”。这并不是说控制反转是不好的-实际上,控制权的基本定义是好的-但是整个框架和实现所有这些目标的工作方法的泛滥几乎是疯狂的,特别是与人们浪费的方式相结合完美而干净的界面,以便能够在99%的时间里注入您永远不会互换的可互换组件。这种事情只能起源于Java企业环境,我很高兴它在其他地方没有那么大的立足点。

通常的论点是,即使您不交换组件,也希望能够与模拟对象等进行隔离测试。但是,恐怕我永远也不会接受这样的说法:为了更好地测试它,值得膨胀和使接口复杂化。测试仅证明一件事-测试有效。另一方面,干净和最少的接口在证明您的代码可以正常工作方面大有帮助。

所以,简短的答案是:是的,但不是您的思维方式。有时,当您需要可互换的行为时,会将一个对象传递给一个指示新对象行为一部分的构造函数。就是这样


5
+1-我同意Java社区似乎过于复杂了,这似乎是一个非常简单的想法。
克里斯·豪

4
这个想法并不一定意味着您需要在生产环境中换掉不同的实现,而是可能需要注入特殊的实现以促进自动化测试。在这方面,DI / IoC当然可以在游戏中有用,但是您希望将其保持在合理范围内。在较高的级别上,您不需要多于一次的服务分辨率-您不想为游戏中的每个小对象(当然也不是每个框架或类似对象)解析服务。
Mike Strobel,2010年

2
@Thomas Dufour:测试的概念永远无法证明任何事情。这只是一个数据点。您可以通过100000次测试,而无需证明它将通过运行100001。证明来自对主题的逻辑分析。我认为这些IoC容器等使测试变得更容易,却以更难证明正确性为代价,我认为这是一件坏事。
Kylotan

13
@Kylotan测试的概念永远不会尝试证明任何东西。它试图证明给定输入的行为符合预期。表现为正确的行为越多,您对整体功能正确的信念就越大,但是永远不会100%。最小接口证明一切的说法是荒谬的。
dash-tom-bang 2010年

5
@Alex Schearer:恐怕我永远无法接受需要使用形式化的IoC容器系统并通常创建其他构造函数参数或工厂类以促进测试的需求,无论如何,这都会使代码得到更好的分解,当然不是使它松散耦合。实际上,它几乎践踏了封装,这是OOP的关键方面。人们不必依靠TDD来进行设计并希望取得良好的结果,而是可以首先遵循SOLID原则。
Kylotan

17

策略模式组合依赖注入都密切相关。

由于策略模式是依赖注入的一种形式,因此,例如,如果您看看像Unity这样的引擎,它们便完全基于此原理。他们对组件(策略模式)的使用被深深地嵌入到他们的整个引擎中。

除了重用组件外,主要好处之一是避免了可怕的深层类层次结构。

这是Mick West的一篇文章,谈到了他如何将这种类型的系统引入Neversoft 的Tony Hawk系列游戏中。

演变您的层次结构

直到最近几年,游戏程序员一直使用深层次的类层次结构来表示游戏实体。潮流开始从使用深层次结构转变为将游戏实体对象构成为组件聚合的各种方法...


2
+1,发展您的层次结构是一个很棒的链接,我完全忘记了。Scott Bilas的幻灯片也很好-正确的链接是(scottbilas.com/files/2002/gdc_san_jose/game_objects_slides.pdf)。还有另一组相关的幻灯片,但我已经忘记了...
leander,2010年

还有Bjarne Rene在《 Games Gems 5》(我认为)中的文章。
克里斯·豪

13

关于控制反转(IoC)模式似乎有很多困惑。许多人将其等同于“策略模式”或“组件模型”,但这些比较并没有真正体现IoC的含义。IoC实际上是关于如何获得依赖关系的。让我给你举个例子:

class Game {
    void Load() {
        this.Sprite.Load(); // loads resource for drawing later
    }
}

class Sprite {
    void Load() {
        FileReader reader = new FileReader("path/to/resource.gif");
        // load image from file
    }
}

在上面很明显,Sprite.Load它依赖于FileReader。当您要测试方法时,您需要:

  1. 已有文件系统
  2. 从文件系统加载的测试文件
  3. 能够触发常见文件系统错误

前两个很明显,但是如果您要确保错误处理能够按预期工作,那么您也确实需要#3。在这两种情况下,由于它们现在都需要进入磁盘,因此可能会大大降低测试速度,并且可能会使测试环境更加复杂。

IoC的目标是使行为的使用与其构造脱钩。请注意,这与策略模式有何不同。使用策略模式的目标是封装行为的可重用块,以便将来可以轻松扩展它;它无话可说。

如果我们要重写Sprite.Load上面的方法,我们可能会得到以下结果:

class Sprite {
    void Load(IReader reader) {
        // load image through reader
    }
}

现在,我们已经将阅读器的构造与使用分开了。因此,可以在测试期间交换测试读取器。这意味着您的测试环境不再需要文件系统,测试文件,并且可以轻松地模拟错误事件。

请注意,我在重写中做了两件事。我创建了一个IReader封装了某些行为的接口-即实现了Strategy模式。另外,我将创建合适的读者的职责转移到了另一个班级。

也许我们不需要新的模式名称来描述上述内容。它给我留下了混合的策略和工厂模式(适用于IoC容器)。话虽这么说,但我不确定人们会基于什么理由反对这种模式,因为很明显它可以解决一个实际问题,而且,当然,这对我来说与Java无关。


2
亚历克斯:没有人反对传入已经创建的对象以在需要时驱动自定义功能。每个人都这样做,就像您的示例一样。问题在于人们正在使用纯粹存在的整个框架来处理这些问题,并认真地对功能的各个方面进行编码以依赖于此功能。他们首先添加的iReader作为参数传递给雪碧建设,然后最终需要特殊SpriteWithFileReaderFactory对象,以推动这项工作,等等。这种态度也从Java之类的东西了Spring IoC容器等起源
Kylotan

2
但是我们不是在谈论IoC容器-至少我在原始文章中没有看到它。我们只是在谈论模式本身。正如我所能告诉的那样,您的论点是“由于一些野蛮工具不好,因此我们不应该使用基础概念。” 把婴儿带洗澡水扔出去使我印象深刻。在简单性,完成工作和可维护性/可测试性之间取得适当的平衡是一个难题,可能最好逐个项目地加以解决。
亚历克斯·谢尔

我怀疑许多人反对IoC,因为这意味着:
phtrivier

4

我会说这是许多工具中的一种,并且偶尔使用。正如tenpn所说,任何引入vtables的方法(通常包括任何其他间接寻址)都可能会降低性能,但这是我们仅需为低级代码担心的事情。对于真正不是问题的高级结构代码,IoC可以带来积极的好处。减少类之间的依赖关系并使代码更灵活的任何方法。


2
确切地说,我担心帧多次调用任何代码的vtable惩罚。这可能是低级的,也可能不是。
tenpn

4

在这一点上,我必须同意Kylotan。“依赖注入”是解决丑陋的Java概念缺陷的丑陋的Java解决方案,而任何人都用其他语言看待它的唯一原因是因为Java成为了很多人的第一语言,而实际上却不是。

另一方面,控制反转是一个有用的想法,已经存在了很长时间,如果做对了,这将非常有帮助。(不是Java / Dependency Injection方法。)实际上,如果您使用的是明智的编程语言,那么您可能一直都在这样做。当我第一次阅读每个人都在嗡嗡作响的整个“ IOC”时,我完全不知所措。控制反转无非就是将函数指针(或方法指针)传递给例程或对象以帮助自定义其行为。

这是自1950年代以来一直存在的想法。它在C标准库中(想到了qsort),并且在Delphi和.NET(事件处理程序/代理)中到处都是。它使旧代码可以调用新代码而无需重新编译旧代码,并且它一直在游戏引擎中得到使用。


2
我当时也曾说过“那么就是人们兴奋的地方吗?” 当我阅读有关DI的文章时,事实是大多数游戏程序员都可以学习它,以及如何将其应用于我们所做的工作。
dash-tom-bang 2010年

2

根据我的经验。很遗憾,因为游戏代码需要非常适应性强,而这些方法肯定会有所帮助。

但是必须说,这两种方法都可以在C ++中引入以前没有的虚拟表,从而带来适当的性能损失。


1

我不是专业的游戏开发人员,只尝试过一次用C ++实现IoC,所以这只是推测。

但是,我怀疑游戏开发人员会对IoC持怀疑态度,因为这意味着:

1 /设计很多接口和很多小类

2 /最近绑定了几乎每个函数调用

现在,这可能是一个巧合,但是与Java相比,两者在C ++中的性能(在游戏中广泛使用,不是吗?)的性能往往更复杂和/或成问题。因为它相对容易实现,可以帮助人们设计更清晰的对象层次结构,并且因为某些人认为它可以将用Java编写应用程序转变为用XML编写应用程序,但这是另一个争论:P)

我对这些评论感兴趣,如果那根本没有任何意义的话;)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.