1

我正在为 RTS 游戏构建 AI。(对于Spring RTS 引擎,如果有人感兴趣的话。)我设置它的方式主要由一组组件组成,这些组件通过触发的事件进行通信。每个组件都有一个方法 handleEvent(Event) 来接收其他组件触发的事件。

事件是一个接口。存在一个层次结构,每个子类都提供有关它们所代表的事件的更详细信息,可以使用特定于该类的 getter 方法来访问这些信息。例如,UnitGainedEvent 类实现了 Event 接口。这个类有一个子类 UnitFinishedEvent(对于任何好奇的人来说,它表示一个单元或建筑物的建造已经完成。)

不是每个组件都会对所有事件感兴趣,所以我想让组件简单地选择他们感兴趣的事件并只接收这些事件。此外,可能的事件集是可扩展的,因此让组件为每种事件类型指定方法不是一个有效的选项。最初我认为访问者模式可能会帮助我解决这个问题,但它失败了,因为它还需要一组固定的事件类型。(我不是 100% 确定我正确理解了模式。如果我错了,请纠正我。)

到目前为止,我发现的唯一解决方案是实现每个组件的 handleEvent(Event) 方法,如下所示:

public int handleEvent(Event event)
{
    if (event instanceof UnitGainedEvent)
    {
        UnitGainedEvent unitGainedEvent = (UnitGainedEvent) event;
        // things to do if I lost a unit
    }

    else if (event instanceof UnitLostEvent)
    {
        UnitLostEvent unitLostEvent = (UnitLostEvent) event;
        // things to do if I lost a unit
    }

    // etc.
}

但是,我真的不喜欢将事件强制转换为特定的 Event 类。现在,记住方法重载可用于根据参数的运行时类型调用不同的方法,我很快想出了一个绝妙的解决方案,既简单又优雅:我可以创建一个带有空实现的基类 handleEvent(事件),并且简单地让子类通过创建方法handleEvent(UnitGainedEvent unitGainedEvent)来接收他们感兴趣的事件,例如。为了确保它可以工作,我设置了一个快速测试用例:

public class Main
{
    public static void main(String[] args)
    {       
        handleEvent(new UnitGainedEvent(null));
    }

    public static void handleEvent(Event event)
    {
        System.out.println("Handling generic Event");
    }

    public static void handleEvent(UnitGainedEvent event)
    {
        System.out.println("Handling UnitGainedEvent");
    }
}

令我非常满意的是,这段代码实际上打印了“Handling UnitGainedEvent”。于是我着手实施。

我的 Component 基类看起来像这样:(嗯,不是真的。这是 Component 类剥离了与我想演示的问题无关的所有内容。)

public class Component
{
    public void handleEvent(Event event)
    {
        System.out.println("Handling Event");
    }
}

这是一个子类的例子:

public class SomeComponent extends Component
{
    public void handleEvent(UnitGainedEvent unitGainedEvent)
    {
        System.out.println("Handling UnitGainedEvent");
    }   
}

为了测试设置,我使用以下主类:

public class Main
{
    public static void main(String[] args)
    {
        Component component = new SomeComponent();
        component.handleEvent(new UnitGainedEvent(null));
    }
}

所以我运行了代码,令我惊讶的是,结果是一个打印整齐的“处理事件”。有趣的是,如果我将组件变量的类型更改为 SomeComponent,它打印出“Handling UnitGainedEvent”。由于某种原因,系统会盲目地调用 Component 类的 handleEvent(Event) 方法,而不是重载 SomeComponent 的 handleEvent(UnitGainedEvent)。(我很想听听 Sun 在这背后的推理,认为这与我的问题并不真正相关 - 不像他们会仅仅因为少数人会发现它是一个非常有用的功能而修复它。)

淘网告诉我其他人也遇到了同样的问题。从我找到的微不足道的信息来看,很少有人,但仍然有人,尽管我发现有关一般方法重载和覆盖的信息比我想知道的要多。但是,最后,我找不到解决方案。

现在,我的(相当明显的)问题是,有没有办法解决这个问题?如果做不到这一点,谁能想到或帮我找到另一个同样方便的解决方案?

编辑:我简直不敢相信我在十分钟后就有了答案。我很惊喜。:) 但是,到目前为止,大多数答案都以一种或另一种形式表明我为每种可能的事件类型制作了一种单独的方法。从技术上讲这是可能的,但这需要我回到代码中并在每次有人提出新的事件类型时添加一个新方法。我学到的是糟糕的编码实践。(另外,我已经有大约 20 多种事件类型,但我什至还没有完成。)相反,我宁愿使用涉及强制转换的解决方案,如上所述。至少这样我可以确保未知的事件类型被简单地忽略,让我可以自由地只处理那些我想使用它们的事件。然而,我真的希望有一个结合了两者优点的解决方案。没有铸造,

非常感谢,Loid Thanead

4

6 回答 6

0

所以我运行了代码,令我惊讶的是,结果是一个打印整齐的“处理事件”。有趣的是,如果我将组件变量的类型更改为 SomeComponent,它会打印出“Handling UnitGainedEvent”。由于某种原因,系统会盲目地调用 Component 类的 handleEvent(Event) 方法,而不是重载 SomeComponent 的 handleEvent(UnitGainedEvent)。

重载并不是这样工作的。您需要在子类而不是超类上调用重载方法。由于您引用的是 Component 类型,Java 不知道您已经在子类中重载了该方法,它只知道您是否已覆盖它。

你真的需要通用的 handleEvent(Event) 类吗?您是否可以为特定的 Event 子类编写 handleEvent() 方法,而不需要 Event 超类的处理程序?

于 2009-02-26T00:38:04.660 回答
0

在上面的示例中,您正在重载基类中的方法,但您没有覆盖它。UnitGainedEvent如果您在您的类中定义了一个抽象(或空)方法来处理它,它将按预期工作Component

您可能需要考虑从不同的角度解决问题。您可以采取的一种方法是对各种听众进行类似于 Swing 中所做的事情。您可以对事件进行逻辑分组,并为这些事件提供多个侦听器。然后,您可以在每个组中提供一个适配器,以便客户端可以覆盖它们,只处理他们感兴趣的特定事件。

于 2009-02-26T00:39:29.960 回答
0

看起来您正在寻找Java 不支持的协变参数(但是,协变返回类型是)。

为什么不使用观察者模式?如果组件总是对特定类型的事件感兴趣,您可以创建一个静态注册表并确保在触发该类型的事件时通知所有感兴趣的组件。

于 2009-02-26T00:40:08.350 回答
0

访问者模式是正确的解决方案。基本上你需要的是多次调度。您想根据组件类型和事件类型选择一种方法,这在 Java 中是不可能的。查看这篇维基百科文章了解更多信息:[ http://en.wikipedia.org/wiki/Multiple_dispatch#Java][1]

[1]:http://多次调度

于 2009-02-26T00:41:08.503 回答
0

由于某种原因,系统会盲目地调用 Component 类的 handleEvent(Event) 方法,而不是重载 SomeComponent 的 handleEvent(UnitGainedEvent)。(我很想听听 Sun 在这背后的推理,认为这与我的问题并不真正相关 - 不像他们会仅仅因为少数人会发现它是一个非常有用的功能而修复它。)

编译器是“愚蠢的”。像这样改变你的代码,你会明白为什么它必须是:

公共类主
{
    公共静态无效主要(字符串 [] 参数)
    {
        组件组件 = new SomeComponent();
        事件事件 = foo();

        组件.handleEvent(事件);
    }
}

foo() 返回某种事件,不知道是什么。由于编译器无法知道返回的是什么子类,并且如果组件支持该方法,它所能做的就是使用它所知道的 - 组件类有一个带有事件的句柄事件。

对于您正在做的一些事情,您应该查看 java.util.Event 和 java.util.EventObject 类型。

于 2009-02-26T00:42:23.587 回答
0

使用反射来获得你想要的效果。当您将事件分派给侦听器时,请查看其方法以找到最佳匹配(您希望编译器为您做的事情)。

您显然希望缓存调用目标,否则性能可能会变得很痛苦。

于 2009-02-26T01:12:54.480 回答