我正在为RTS游戏构建AI。 (对于Spring RTS engine,如果有人感兴趣的话。)我设置它的方式主要包括一组通过触发事件进行通信的组件。每个组件都有一个方法handleEvent(Event),它接收由其他组件触发的事件。
事件是一个界面。存在层次结构,每个子类提供有关它们所代表的事件的更详细信息,可以使用特定于该类的getter方法来访问它们。例如,UnitGainedEvent类实现了Event接口。这个类有一个子类UnitFinishedEvent(它表示一个单元或建筑物的构造已经完成,对于任何好奇的人来说。)
并非每个组件都会对所有事件感兴趣,因此我想让Components只选择他们感兴趣的事件并仅接收那些事件。此外,可能的事件集是可扩展的,因此让组件为每个事件类型指定方法不是一个有效的选项。最初我认为访问者模式可能会帮助我解决这个问题,但它失败了,因为它还需要一组固定的事件类型。 (我不是100%确定我理解正确的模式。如果我错了,请纠正我。)
到目前为止,我发现的唯一解决方案是实现每个Component的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'。所以我着手实施。
My 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,它会打印'处理UnitGainedEvent'。 由于某种原因,系统盲目地调用Component类的handleEvent(Event)方法,而不是重载SomeComponent的handleEvent(UnitGainedEvent)。 (我会有兴趣听到Sun背后的推理,认为这与我的问题无关 - 不像他们会解决它只是因为少数人会发现它是一个非常有用的功能。)
淘网时告诉我其他人遇到了同样的问题。尽管我发现有关一般方法重载和覆盖的更多信息比我想知道的更多,但是我找到的信息量很少,但人们很少。但是,最终,我找不到解决方案。现在,我的(相当明显的)问题是,有什么方法可以解决这个问题吗?如果做不到这一点,任何人都可以想到或帮助我找到同样方便的另一种解决方案吗?
编辑:我很难相信我只有十分钟后才能得到答案。我很惊喜。 :) 但是,到目前为止,大多数答案以某种形式建议我为每种可能的事件类型制作一个单独的方法。从技术上讲,这是可能的,但这需要我回到代码中并在每次有人提出新的事件类型时添加新方法。我学到的是糟糕的编码习惯。 (另外,我已经有20多种事件类型了,我还没有完成。) 相反,我宁愿使用涉及铸造的解决方案,如上所述。至少这样我可以确保简单地忽略未知的事件类型,让我自由地只处理那些我想要使用它们的事件。 我真的希望找到一种结合了两者中最好的解决方案。没有强制转换,也没有回到每个事件类型的代码中。
非常感谢, Loid Thanead
答案 0 :(得分:0)
所以我运行代码,并且对我很好 出乎意料的是,结果很整齐 打印'处理事件'。 有趣的是,如果我改变了 组件变量的类型 SomeComponent,它确实打印'处理 UnitGainedEvent“。出于某种原因或 另外,系统盲目地调用 组件类' handleEvent(Event)方法,而不是if 重载SomeComponent的 的handleEvent(UnitGainedEvent)。
重载并不是真的那样。您需要在子类上调用重载方法,而不是超类。由于您引用了Component类型,因此Java不知道您在子类中重载了该方法,它只知道您是否重写。
你真的需要通用的handleEvent(Event)类吗?您是否可以为特定的Event子类编写handleEvent()方法而无需事件超类的处理程序?
答案 1 :(得分:0)
在上面的示例中,您从基类重载方法,但您没有覆盖它。如果您定义了处理UnitGainedEvent
类中Component
的抽象(或空)方法,它将按预期工作。
您可能需要考虑从不同角度攻击问题。 您可以采取的一种方法是执行类似于Swing中使用各种侦听器所做的操作。 您可以逻辑地对事件进行分组,并为这些事件提供大量侦听器。然后,您可以在每个组中提供适配器,以便客户端可以覆盖它们,仅处理它们感兴趣的特定事件。
答案 2 :(得分:0)
看起来你正在寻找covariant个参数,这些参数在Java中不受支持(但是,协变返回类型)。
为什么不使用observer pattern?如果组件始终对特定类型的事件感兴趣,则可以创建静态注册表,并确保在触发该类型的事件时通知所有感兴趣的组件。
答案 3 :(得分:0)
访客模式是正确的解决方案。基本上你需要的是多次派遣。您希望根据组件类型和Java中不可能的事件类型选择方法。有关详细信息,请查看此维基百科文章:[http://en.wikipedia.org/wiki/Multiple_dispatch#Java][1]。
[1]:http://Multiple发货
答案 4 :(得分:0)
由于某种原因,系统 盲目地调用Component类' handleEvent(Event)方法,而不是if 重载SomeComponent的 的handleEvent(UnitGainedEvent)。 (我会 有兴趣听太阳报 这背后的推理,想到了 与我的问题无关 - 不喜欢他们会因为a而修复它 少数人会发现它非常 有用的功能。)
编译器是“愚蠢的”。像这样更改您的代码,您将看到它为什么必须:
public class Main { public static void main(String[] args) { Component component = new SomeComponent(); Event event = foo(); component.handleEvent(event); } }
foo()返回某种事件,不知道是什么。由于编译器无法知道返回了什么子类,并且如果Component支持该方法,它所能做的只是它所知道的 - Component类有一个handleEvent,它接受一个Event。
对于你正在做的一些事情,你应该看一下java.util.Event和java.util.EventObject类型。
答案 5 :(得分:0)
使用反射来获得所需的效果。当您向侦听器分派事件时,请查看其方法以找到最佳匹配(您希望编译器为您做什么)。
您显然希望缓存调用目标,否则性能可能会变得很痛苦。