我是否错误地使用了 instanceof?

4
在我的简单游戏引擎中,我使用一个名为“UpdatedGameElement”的接口来向对象发出信号,表明该对象必须通过update()方法在每个帧中更新。现在,在我的“main”类中,我遍历了一个GameElement列表,并检查它们是否是UpdatedGameElement的实例。如果是这种情况,我会强制转换它们,然后调用.update()方法。
现在问题是,我刚刚读到,使用instanceof通常是糟糕编码的标志;在类被用作标记时可以轻松替换为变量的情况下,我同意。但对于我的情况,我不是很确定。
我想,我可以让GameElement类实现UpdatedGameElement,并定义一个标准的空update()方法,需要覆盖以实际执行某些操作,但我不确定那是否比我现在拥有的更好。
你怎么看?
public void process()
{
    if (active)
    {
        for (GameElement GE: elements)
        {
            if (!GE.isToBeRemoved())
            {
                //Relevant part
                if (GE instanceof UpdatedGameElement)
                {
                    ((UpdatedGameElement) GE).update();
                }           
            }
            else
            {
                prepareRemoval(GE);
            }
        }  
        processRemovals();
    }
}

为什么不直接更新所有的GameElements呢?或者将它们存储在自己的列表中? - David Ehrmann
2
我们需要看到代码。instanceof并不一定是坏的,但这种情况表明你最好持有一个单独的数据结构,其中只包含可更新的对象。 - chrylis -cautiouslyoptimistic-
@DavidEhrmann:类GameElement不可更新。到目前为止,一个类必须实现UpdatedGameElement才能“可更新”(即具有void update())。是的,你是对的:我可以让GameElement自己实现它,但我只是好奇是否可以同时使用两种方法。(至于为什么:我的当前方法会使类更加清晰,因为它们不需要拥有可能使用子类的一千个方法。)还发布了一些代码 :) - Jonas Bartkowski
@Christian:我把它们都放在一个列表中,因为……实际上我也不知道为什么。我想我只是喜欢在低端尽可能地抽象化。 至于如何创建和添加这些元素;必须定义一个非抽象类扩展GameElement,然后通过elements.add()进行实例化和添加。 - Jonas Bartkowski
有一个很好的地方可以问这样的问题:http://codereview.stackexchange.com/ - user784540
显示剩余5条评论
3个回答

2

根据OP的邀请:

如果使用接口的唯一原因是为GE添加标记和更新方法,并且在此单个instanceof之后未使用类型UGE,则这是拥有这些额外类型的一个薄弱理由。特别是当可以将更新能力扩展到所有其他GE,其中它只是NOOP时。

基类中的抽象方法迫使程序员决定是否需要为特定子类编写更新代码。从“防御性设计”的角度来看,这种方法相当“安全”。但是,当然,你需要编写更多的代码。

与前一技术相比:如果你忘记了接口,就没有警报。

此外,如果在基类中编写一个NOOP更新方法,并依赖程序员的敏捷性在必要时进行覆盖:方便,但在你忘记这样做时存在风险。

总结:有一些微妙的优缺点-不仅仅是instanceof“气味”。


完美的答案,涵盖了我所有问题的每一个点 :)谢谢。 - Jonas Bartkowski

0
根据您的评论,您提到GameElement实现了UpdatedGameElement,目前GameElement是唯一实现UpdatedGameElement的类,但未来可能会有更多。此外,希望您知道接口不能被实例化,因此您无法创建UpdatedGameElement的实例,实际上是创建接口实现类的实例。因此,在运行时,当您创建GameElement的实例并将其分配给UpdatedGameElement变量时,这并不意味着该实例现在是UpdatedGameElement类型,它实际上是GameElement类型,现在假设您有另一个实现XYZElement实现UpdatedGameElement的类,并创建以下实例:
UpdatedGameElement ge = new GameElement();
UpdatedGameElement xyze = new XYZElement();

你认为像下面这样使用实例检查好吗,因为在任何情况下都是真的,而且你永远不知道ge和xyze是哪种实例。
if(ge instance of UpdatedGameElement)
instead one should always check for if(ge instance of GameElement)

Similarly for if(xyze instance of UpdatedGameElement)
instead one should always check for if(ge instance of XYZElement)

Hope this helps.

UpdatedGameElement是一个接口。因此,实现它的每个对象实际上都是UpdatedGameElement类型。 - Jonas Bartkowski
同时,GameElement是抽象的,应该是所有其他GameElement的基类(就像Java的Object类一样)。因此不应该有冲突,因为您无法实例化它,也没有其他类在引擎中拥有绝对必要但不包含在GameElement中的内容。我想问的是,在我概述的情况下,是否可以将我的UpdatedGameElement接口用作标记。如果我表述得不够清楚,我很抱歉。 - Jonas Bartkowski

0
我想我可以让GameElement类实现UpdatedGameElement,并定义一个标准的空update()方法,需要重写以实际执行某些操作。
是的,你绝对应该这样做!
原因:假设将来您需要实现另一个扩展UpdatedGameElement的“可更新”类-考虑您必须在每个使用instanceof的地方进行的代码更改...

我不需要做出改变,是吗?UpdatedGameElement 的唯一用途是发出信号,表明扩展对象中存在可以调用的 update() 方法。该方法在每个单独的类中定义。 - Jonas Bartkowski
@JonasBartkowski,除非新类扩展/实现了“UpdatedGameElement”,否则您肯定需要进行更改。现在,如果新类无法扩展/实现“UpdatedGameElement”(我可以想到至少几种情况,您将无法这样做),则必须在代码中使用它的每个位置添加另一个“instanceof”检查。 - Nir Alfasi
不,如果它没有实现UpdatedGameElement,它应该什么也不做,因此不存在任何特殊行为需要更改的情况。 - Jonas Bartkowski
@JonasBartkowski 你没有理解我写的内容 - 请再读一遍。 - Nir Alfasi
我认为我读对了。实际上,我已经有5个扩展UpdatedGameElement的类,而且我不需要改变任何东西。 它作为一个标记的整个意义在于,如果您不需要该功能,则无需执行任何操作。 - Jonas Bartkowski
1
如果是这样的话,那你可以继续使用它。虽然这不是我所说的“干净”的代码,但你并不是第一个使用接口来实现标记模式(Serializable、Clonable等)的人。 - Nir Alfasi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接