gpt4 book ai didi

java - 如何解耦事件驱动模块?

转载 作者:塔克拉玛干 更新时间:2023-11-02 19:51:14 26 4
gpt4 key购买 nike

可能需要一些背景知识,但如果您有信心,请跳至问题。希望摘要能够说明问题。

总结

我有一个 InputDispatcher它将事件(鼠标、键盘等)分派(dispatch)到 Game对象。

我想缩放 InputDispatcher独立于Game : InputDispatcher应该能够支持更多的事件类型,但是Game不应被迫使用所有这些。

背景

本项目使用 JSFML。

输入事件通过 Window 处理通过 pollEvents() : List<Event> 上课.您必须自己进行调度。

我创建了一个 GameInputDispatcher类将事件处理与诸如处理窗口框架之类的事情分离。

Game game = ...;
GameInputDispatcher inputDispatcher = new GameInputDispatcher(game);

GameWindow window = new GameWindow(game);

//loop....
inputDispatcher.dispatch(window::pollEvents, window::close);
game.update();
window.render();

此示例的循环已被简化

class GameInputDispatcher {
private Game game;

public GameInputDispatcher(Game game) {
this.game = game;
}

public void dispatch(List<Event> events, Runnable onClose) {
events.forEach(event -> {
switch(event.type) {
case CLOSE: //Event.Type.CLOSE
onClose.run();
break;
default:
// !! where I want to dispatch events to Game !!
break;
}
}
}
}

问题

在上面的代码 ( GameInputDispatcher ) 中,我可以将事件分派(dispatch)给 Game通过创建 Game#onEvent(Event)并调用 game.onEvent(event)在默认情况下。

但这会强制 Game编写用于排序和调度鼠标和键盘事件的实现:

class DemoGame implements Game {
public void onEvent(Event event) {
// what kind of event?
}
}

问题

如果我想提供来自 InputDispacher 的事件进入Game ,我如何在避免违反接口(interface)隔离原则的情况下这样做? (通过声明所有监听方法:onKeyPressed, onMouseMoved , etc.. inside of Game`,即使它们可能不会被使用)。

Game应该能够选择它想要使用的输入形式。支持的输入类型(例如鼠标、按键、操纵杆……)应该通过 InputDispatcher 进行缩放。 ,但是Game不应被迫支持所有这些输入。

我的尝试

我创造了:

interface InputListener {
void registerUsing(ListenerRegistrar registrar);
}

Game将扩展此接口(interface),允许 InputDispatcher依赖InputListener并调用 registerUsing方法:

interface Game extends InputListener { }

class InputDispatcher {
private MouseListener mouseListener;
private KeyListener keyListener;

public InputDispatcher(InputListener listener) {
ListenerRegistrar registrar = new ListenerRegistrar();
listener.registerUsing(registrar);

mouseListener = registrar.getMouseListener();
keyListener = registrar.getKeyListener();
}

public void dispatch(List<Event> events, Runnable onClose) {
events.forEach(event -> {
switch(event.type) {
case CLOSE:
onClose.run();
break;
case KEY_PRESSED:
keyListener.onKeyPressed(event.asKeyEvent().key);
break;
//...
}
});
}
}

Game子类型现在可以实现任何受支持的监听器,然后自行注册:

class DemoGame implements Game, MouseListener {
public void onKeyPressed(Keyboard.Key key) {

}

public void registerUsing(ListenerRegistrar registrar) {
registrar.registerKeyListener(this);
//...
}
}

尝试问题

虽然这允许Game子类型只实现他们想要的行为,它强制任何Game申报registerUsing ,即使他们没有实现任何监听器。

这可以通过制作 registerUsing 来解决。一个default方法,让所有听众扩展 InputListener重新声明方法:

interface InputListener {
default void registerUsing(ListenerRegistrar registrar) { }
}

interface MouseListener extends InputListener {
void registerUsing(ListenerRegistrar registrar);

//...listening methods
}

但是对于我选择创建的每个听众来说,这样做会非常乏味,违反了 DRY。

最佳答案

我看不到 registerUsing(ListenerRegistrar) 有任何意义。如果必须编写监听器外部的代码,它知道这是一个监听器,因此它需要向 ListenerRegistrar 注册,那么它也可以继续向注册器注册监听器。

您问题中所述的问题通常在 GUI 中通过默认处理处理,使用继承或委托(delegate)。

通过继承,您将拥有一个基类,将其称为 DefaultEventListenerBaseEventListener,无论您喜欢什么,它都有一个 public void onEvent(Event event) 方法,它包含一个 switch 语句,该语句检查事件的类型并为它知道的每个事件调用自身的重写。这些可覆盖项通常什么都不做。然后您的“游戏”派生自此 DefaultEventListener 并仅为它关心的事件提供覆盖实现。

通过委派,您有一个 switch 语句,您可以在其中检查您知道的事件,以及在 switchdefault 子句中> 你委托(delegate)给一些 DefaultEventListener 类型的 defaultEventListener,它可能什么都不做。

有一种结合了两者的变体:如果事件监听器处理了事件,则返回 true,在这种情况下,switch 语句会立即返回,这样事件就不会被进一步处理,如果他们不处理事件,则为 false,在这种情况下,switch 语句 breaks,所以代码在switch 语句取得控制权,它所做的是将事件转发给其他一些监听器。

另一种方法(例如在 SWT 中的许多情况下使用)涉及为您可以观察的每个单独事件注册一个观察者方法。如果你这样做,那么一定要记得在你的游戏对象死亡时注销每个事件,否则它会变成僵尸。为 SWT 编写的应用程序充满了由 gui 控件引起的内存泄漏,这些 gui 控件永远不会被垃圾收集,因为它们在某处注册了一些观察者,即使它们早已关闭并被遗忘。这也是 bug 的来源,因为这样的僵尸控件不断接收事件(例如,键盘事件)并不断尝试做一些响应,即使它们不再有 gui。

关于java - 如何解耦事件驱动模块?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42034243/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com