如何避免在MVC体系结构中使用instanceof

我正在用Java开发类似Pacman的游戏,而我目前正遇到这些我不喜欢的代码味道。

让我解释一下我的想法:我的游戏是建立在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()));
}

第二种气味与鬼魂有关,我的游戏具有状态模式,如果状态发生变化,则可能会改变鬼魂的颜色。例如,如果状态为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

保持安全!

hao521ye_88 回答:如何避免在MVC体系结构中使用instanceof

您似乎正在寻找的是Abstract Factory模式。有几种方法可以实现它;这可能是您所需的最佳搭配:

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);
,

当您打算分离关注点时,这实际上是一个非常普遍的问题。例如,如果未将渲染与模型隔离开,那么您将有一个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等)引入专门的接口,并直接在开机时实施这些操作。边界现在仅由接口绘制,但它们仍然存在。

最后,您也可以要求其各自的渲染器或声音播放器通电,而不是直接由对象执行操作。我想我在这里比较喜欢访客,但是根据您所期望的复杂程度,有很多方法可以给猫咪剥皮。这都是权衡取舍的方法!

本文链接:https://www.f2er.com/2360030.html

大家都在问