【问题标题】:How to avoid using instanceof in MVC Architecture如何避免在 MVC 架构中使用 instanceof
【发布时间】:2020-08-30 09:40:18
【问题描述】:

我正在用 Java 开发一个像吃豆人这样的游戏,我目前正面临这些我不喜欢的代码味道。

让我解释一下我的想法:我的游戏是建立在 MVC 架构上的。在视图模块上,我查找模型上的每个电源,并将其添加到要在 GUI 上绘制的元素列表中。问题是我有 3 种使用接口的加电装置,所以当我添加加电装置时,我需要检查它们是什么类型,然后添加对应的视图。让我显示一些代码,以便我更清楚:

for (PowerUp powerUp : level.getPowerUps()) {
   if (powerUp instanceof Invincibility) elements.add(new InvincibilityView(powerUp.getPosition()));
   if (powerUp instanceof Freeze) elements.add(new FreezeView(powerUp.getPosition()));
   if (powerUp instanceof Fright) elements.add(new FrightView(powerUp.getPosition()));
}

第二个气味和鬼有关,我的游戏有一个State Pattern,如果状态改变,它应该改变鬼的颜色。例如,如果状态是Frightened,我希望鬼是橙色,如果状态是Frozen,我希望鬼是蓝色,等等。

因此,在创建 Ghost 视图时,我通过参数传递状态,它会检查(再次使用 instanceof)当前处于什么状态。让我再展示一些代码:

public void draw(graphics) {
    String color = "#FF0000";
    if (state instanceof Invincible) color = "#585858";
    if (state instanceof Frozen) color = "#00FFFF";
    if (state instanceof Frightened) color = "#FF7F50";
    // draw ghost
}

我的问题是如何在不更改模型模块的情况下避免使用instanceof

保持安全!

【问题讨论】:

  • 您可能正在寻找一个抽象工厂。请注意,ViewFactory 可以有一个方法 canHandlePowerup(PowerUp),然后您可以遍历工厂列表以找到处理特定上电的工厂并请求查看。
  • 您是否尝试过使用接口来执行操作并使用枚举来获取颜色?
  • 我尝试了@chrylis-onstrike-指出的解决方案,我现在为每个ghost 和powerUp 视图提供了工厂,以及一个从列表中选择正确工厂的FactoryProducer。这确实是我一直在寻找的,我现在有很多新文件,但只需添加新工厂,代码就可以轻松扩展。谢谢。

标签: java oop model-view-controller refactoring instanceof


【解决方案1】:

当您旨在分离关注点时,这实际上是一个非常常见的问题。例如,如果渲染未与模型分离,那么您将拥有一个 PowerUp.render() 方法,以便他们可以渲染自己并收工。

但是,您会遇到另一个问题,即各种问题最终会集中在一个类中。这是一种权衡,您要么直接在对象上实现操作,要么提取这些行为,但随后必须以某种方式执行类型匹配。

抽象工厂在这里可能很有趣,因为它减少了您必须做出的基于类型的决定的数量,但如果您需要做出其他基于类型的决定(例如不同的通电声音),您将不得不再次进行类型匹配,没有办法了。

话虽如此,您可以使用Visitor Pattern 实现可重用的类型安全机制进行类型匹配。

例如(为简洁起见,省略了访问修饰符)

interface PowerUpVisitor {
    visit(Invincibility powerUp);
    visit(Freeze powerUp);
    visit(Fright powerUp);
}

interface PowerUp {
    accept(PowerUpVisitor visitor);
    ...
}

class Freeze implements PowerUp {
    accept(PowerUpVisitor visitor) { visitor.visit(this); }
}

class PlayPowerUpSound implements PowerUpVisitor {
    visit(Invincibility powerUp) { playInvicibilitySound(); }
    ...
}

class RenderPowerUp implements PowerUpVisitor {
    visit(Invincibility powerUp) { renderInvisibility(); }
    ...
}

//In practice you would most likely reuse the same visitor instances
somePowerUp.accept(new PlayPowerUpSound()); //to play sound
somePowerUp.accept(new RenderPowerUp()); //to render

这种模式的主要优点是现在编译器会告诉你是否忘记处理特定类型的加电,这与 instanceof 检查不同。

与其他提议的抽象工厂解决方案一样,使用基于动态类型的解析,您必须使用反射实现基于运行时的验证,以确保所有类型都存在工厂。

对于抽象语法树之类的分层结构,人们通常会自然而然地想到访问者模式,但它在这里同样有用。

如果您同意让隔离不那么严格,还有其他方法。例如,您可以为PowerUp 可能具有的各种行为引入一个专门的接口,例如IPlaySoundIHaveAView 等,并直接在powerups 中实现这些操作。边界现在完全由接口绘制,但它们仍然存在。

最后,您还可以向 powerups 询问它们各自的渲染器或声音播放器,而不是直接由对象执行操作。我想我更喜欢这里的访客,但是根据您期望的复杂性等,有很多方法可以给猫剥皮。这都是权衡取舍!

【讨论】:

  • 这很有帮助,我必须说我不熟悉这种模式,但我才刚刚开始使用 OOP。我认为这对我来说甚至比抽象工厂更好。谢谢。
  • 很高兴能帮上忙!模式匹配来自函数式编程,访问者模式是它的 OO 实现。也许 java 将来会为此提供一种语法,但它并没有那么多样板。另请注意,抽象工厂通常用于一系列对象。例如。 WindowsUIComponents 与 MacUIComponents,两者都允许您创建特定于操作系统的按钮、窗口等,而不仅仅是单个组件。
【解决方案2】:

您正在寻找的似乎是抽象工厂模式。有几种方法可以实现它;这可能是您需要的最佳匹配:

interface ViewFactory<V extends View> {
    boolean canHandlePowerUp(PowerUp target);
    V createView(PowerUp target);
}

class FreezeViewFactory extends ViewFactory<FreezeView> { ... }

...

List<ViewFactory<? extends V>> factories;

View v = factories.stream()
    .filter(f -> f.canHandlePowerUp(p))
    .findFirst()
    .map(f -> createView(p))
    .orElseThrow(() -> new IllegalStateException("no ViewFactory found for " + p);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-04-18
    • 2022-11-15
    • 2011-09-03
    • 2013-03-05
    • 1970-01-01
    • 2019-10-12
    • 2012-08-13
    • 1970-01-01
    相关资源
    最近更新 更多