【问题标题】:OOP design - many objects each with unique interactions to limited subset of othersOOP 设计 - 许多对象每个都与其他对象的有限子集具有独特的交互
【发布时间】:2013-03-06 17:15:04
【问题描述】:

如果类 A 与类 B、C 和 D 中的每一个都有唯一的交互,那么交互的代码应该在 A 中还是在 B、C 和 D 中?

我正在编写一个小游戏,其中许多对象可以与其他对象进行独特的交互。例如,EMP 命中 sentry gun 并禁用它。它还可以击中grenade 并引爆它,也可以击中player 并施加减速效果。

我的问题是,这段代码应该放在 EMP 类中,还是分散在所有其他类中?我的直觉告诉我要多态地执行此操作,以便我只需告诉每个类处理它喜欢的 EMP 罢工,这将允许我添加更多项目并更改它们处理 EMP 的方式,而无需更改 EMP 代码。

但是,EMP 目前只与我的 50 个对象中的大约 4 个进行交互,因此用空的 RespondToEMP() 调用填充 46 个对象似乎是错误的。这似乎也有点不直观,如果我决定删除 EMP,我需要更改所有其他类,而 EMP 类本身最终会变得非常小。这也意味着如果我想改变 EMP 的行为方式,我需要查看所有不同的类以找到所有可能的用法。此外,如果 EMP 有一些通用效果,例如爆炸,那么这种效果肯定会在 EMP 代码中,远离所有其他会分发的效果。

【问题讨论】:

  • 您没有提到您使用的是什么编程语言,但这听起来像是协议(在 Objective-C 中)或接口(在 Java 中)的完美用例。然后,当您的 EMP 引爆时,它可以检查范围内的对象是否符合协议,如果符合,则调用协议方法。
  • 最好有一个中心类来处理这些交互。 IE。一个“交互”类,其方法在每次发生可导致交互的动作时调用。然后,您可以遍历“范围内”的所有对象并说if action is EMP and target conforms to protocol EMP then send EMP_DETONATED message to target。这样,当您添加新交互时,所有代码都在一个位置,您只需在适当的类中实现协议。

标签: oop polymorphism


【解决方案1】:

我的想法是sentry gungrenadeplayer 都应该实现一个通用接口来执行RespondToEMP 方法。这样,如果您稍后决定其他 46 个对象中的一个可以被 EMP 击中,您就会知道要实现此接口。

【讨论】:

  • 好的,那么我有 3 个实现接口的对象,还有 47 个没有实现接口的对象。当我的 EMP 爆炸时,我知道它击中了一些物体,我怎么知道它击中的物体是否实现了这个接口?
  • 我不确定我是否理解这个问题,但您应该可以使用 reflection 来解决这个问题。所有关于“命中”的逻辑都将在实现接口的类中处理。
  • 让我澄清一下 - 我的 emp 爆炸了,引擎给了我一个 EMP 击中的实体列表。这些实体中只有少数需要与 EMP 交互。因此,您是说依次检查每个对象是否使用反射实现了接口,然后调用该类函数?你不是在有效地检查类型以切换逻辑,基本上与多态性应该完成的相反吗?
  • 为什么需要知道哪些对象实现了接口?实现它的对象会在 EMP 爆炸时执行他们应该做的任何事情(因为他们订阅了 EMP exloded 事件),如果其他代码需要知道这些对象在 EMP 爆炸时做了什么,您可以实现回调或发布其他代码可以订阅的事件...
  • 这就是我在上面评论中的建议。当您检查通用对象是否实现接口(或符合协议,具体取决于您的语言)时,它与多态性并不相反。你不关心对象是什么,只关心它是否想和你说话。
【解决方案2】:

您可能需要考虑http://en.wikipedia.org/wiki/Visitor_pattern。如果您有超过 2 件事一起互动,请查看我对Managing inter-object relationships的回答

【讨论】:

    【解决方案3】:

    你是对的,拥有一个虚拟方法是一种代码味道。有几种方法可以解决这个问题:

    如果您希望添加更多对象,

    切换语句来处理事件效果很好,因为您将避免在添加更多对象时修改事件:

    enum Event { EMP, Bomb }
    
    class SentryGun {
        void handleEvent(Event e) { switch(e) {
            case EMP: disable();
        }}
        void disable() {}
    }
    class Grenade {
        void handleEvent(Event e) { switch(e) {
                case EMP: detonate();
        }}
        void detonate() {}
    }
    
    如果您希望添加更多事件,

    访问对象的事件效果很好,因为您不必修改现在可以对该事件做出反应的每个对象:

    class EMP {
        void hit(SentryGun object) {
            object.disable();
        }
    
        void hit(Grenade object) {
            object.detonate();
        }
    }
    

    【讨论】:

    • 当您不是指大多数人认为的事件(观察者模式和/或 .NET 事件)时,提及事件有点令人困惑。您在这里所做的是有效地创建一个枚举来表示类类型,然后打开它,这似乎与强制转换对象并检查 null 一样糟糕。每次创建新类时,都必须更新双方都依赖的枚举列表。至少有一个演员,你不能忘记这个额外的步骤。在您的第二个示例中,您假设您已经投射了对象,并且我在问题中概述的所有问题仍然适用。
    【解决方案4】:

    这不是最优雅的解决方案,但您可以做的一件事是拥有一个您的对象扩展的基类,该基类具有事件的默认行为,如果有事情要做,对象可以覆盖它。

    【讨论】:

      【解决方案5】:

      在某些情况下,您需要区分可以交互或不能交互的对象。共有三个选项:

      • 具有可能为空实现的通用接口
      • 接口并检查对象是否实现它
      • 定义一些包含交互和反应的地图/表格

      由于前两个在其他答案中出现,我将描述第三个选项。 我们定义映射:Thing x Object -> Command。在这种情况下,EMP_hit 将是 Thing(也许名称可能更好..),可能包含有关其来源的信息。对象将是你的所有东西。命令类似于命令模式(参见http://en.wikipedia.org/wiki/Command_pattern)。我会将其存储在地图而不是表格中,以仅保留非空命令并使用一些字符串 ID,这些 ID 将在同一类的事物/对象之间共享,以在此映射中找到正确的条目。

      此解决方案的优点是您可以在某些配置文件中定义它或/并在运行时动态更改。无需重新编译即可更改配置

      【讨论】:

      • 有趣的想法,但这最终不会有相同的代码用于类型检查对象和基于结果切换逻辑吗?即if (source is EMP && target is SentryGun) Disable()?或者,如果我调用特定于 EMP 的函数:if (target is SentryGun) Disable()。在那种情况下,我不妨将代码放入 EMP:if (target is SentryGun) Disable()
      • 是和否 ;) 在这种情况下,您的命令可能具有函数 do_command(thing, object) -> void 或 status。我不确定是否有必要检查类型,就好像你正确创建了这个地图/表一样,你应该保证正确的类型 - 所以你只需将对象向下转换为预期的类型。没有ifswitch。优点是您可以轻松更改行为 - 例如,当游戏设置更改为不同的植物时,EMP 可能对任何东西都不起作用 - 您只需使用不同的表格或修改现有的表格。您将此操作定义与所涉及的对象分开。
      • 更正:您将对象定义与操作分开。我的意思也是行星 - 不是植物......
      猜你喜欢
      • 1970-01-01
      • 2013-12-30
      • 2020-12-04
      • 2014-05-12
      • 2016-09-22
      • 1970-01-01
      • 1970-01-01
      • 2011-01-02
      • 1970-01-01
      相关资源
      最近更新 更多