避免Java中的instanceof

时间:2013-03-17 04:47:19

标签: java oop inheritance instanceof

我在大学的某个阶段被告知(并且随后在十几个地方读过),使用instanceof只能用作“最后的手段”。考虑到这一点,是否有人能够告诉我以下代码是否是最后的手段。我已经看过堆栈溢出但是找不到类似的情况 - 也许我错过了它?

private void allocateUITweenManager() {
   for(GameObject go:mGameObjects){
      if (go instanceof GameGroup) ((GameGroup) go).setUITweenManager(mUITweenManager);
   }
}

,其中

  • mGameObjects是一个数组,其中只有一些是GameGroup类型
  • GameGroup是抽象类GameObject的子类。
  • GameGroup使用具有方法UITweenable
  • 的接口setUITweenManager()
  • GameObject不使用接口UITweenable

我想我可以同样(也可能应该)在上面的代码中用GameGroup替换UITweenable - 我会问同样的问题。

还有另一种方法可以避免instanceof吗?这段代码不能失败,因此(我认为,对吧?),但是鉴于新闻instanceof看起来很糟糕,我是否已经在我使用instanceof的某个地方犯了一些OOP的主要罪行?这里?

提前致谢!

9 个答案:

答案 0 :(得分:5)

我在大学的Compiler课程中学到了Visitor pattern,我认为它可能适用于你的场景。请考虑以下代码:

public class GameObjectVisitor {

    public boolean visit(GameObject1 obj1) { return true; }
    .
    .
    // one method for each game object
    public boolean visit(GameGroup obj1) { return true; }
}

然后你可以在GameObject界面中添加一个方法,如下所示:

public interface GameObject {

    .
    .
    public boolean visit(GameObjectVisitor visitor);
}

然后每个GameObject实现此方法:

public class GameGroup implements GameObject {

    .
    .
    .
    public boolean visit(GameObjectVisitor visitor) {
        visitor.visit(this);
    }
}

当您具有GameObject的复杂继承层次结构时,这非常有用。对于您的情况,您的方法将如下所示:

private void allocateUITweenManager() {

    GameObjectVisitor gameGroupVisitor = new GameObjectVisitor() {
        public boolean visit(GameGroup obj1) {
            obj1.setUITweenManager(mUITweenManager);
        }
    };

    for(GameObject go:mGameObjects){
      go.visit(gameGroupVisitor);
   }
}

答案 1 :(得分:4)

修改

您可以在此处执行两项主要操作来减轻instanceof的特定实例。 (双关语?)

  1. 按照我的初步答案建议并将您要定位的方法移动到您正在迭代的类。在这种情况下,这并不理想,因为该方法对父对象没有意义,并且会像Ted所说的那样污染。

  2. 将正在迭代的对象的范围缩小为仅熟悉目标方法的对象。我认为这是更理想的方法,但在当前的代码形式中可能无法实现。

  3. 就个人而言,我避免像瘟疫一样instanceof因为它让我觉得我完全错过了某些东西,但有时候它是必要的。如果您的代码以这种方式布局,并且您无法缩小正在迭代的对象的范围,那么instanceof可能会正常工作。但这似乎是一个很好的机会,可以看到多态如何使您的代码在未来更容易阅读和维护。

    我将在下面留下原始答案,以保持评论的完整性。

    <强> /修改

    就个人而言,我并不认为这是使用instanceof的充分理由。在我看来,你可以利用一些多态来实现你的目标。

    您是否考虑过将setUITweenManager(...)作为GameObject的方法?这样做有意义吗?

    如果它确实有意义,您可以让默认实现不执行任何操作,并让GameGroup覆盖该方法以执行您希望它执行的操作。此时,您的代码可能就像这样:

    private void allocateUITweenManager() {
       for(GameObject go:mGameObjects){
           go.setUITweenManager(mUITweenManager);
       }
    }
    

    这是多态性的行动,但我不确定它是否适合您当前的情况。如果可能的话,迭代CollectionUITweenable对象会更有意义。

答案 2 :(得分:2)

不鼓励instanceof的原因是因为在OOP中我们不应该从外部检查对象的类型。相反,惯用的方法是让对象本身使用覆盖方法。在您的情况下,一种可能的解决方案是在boolean setUITweenManager(...)上定义GameObject,如果为特定对象设置管理器,则让它返回true。但是,如果在许多地方出现这种模式,顶级类可能会受到严重污染。因此有时instanceof是“较小的邪恶”。

这种OPP方法的问题是每个对象必须“知道”所有可能的用例。如果您需要一个适用于您的类层次结构的新功能,您必须将它自己添加到类中,您不能将它放在单独的某个位置,就像在不同的模块中一样。正如其他人所建议的那样,这可以使用visitor pattern以一般方式解决。访问者模式描述了检查对象的最常用方法,并且在与多态性结合时变得更加有用。

请注意,其他语言(特别是函数式语言)使用不同的原则。它们不是让对象“知道”它们如何执行每个可能的操作,而是声明自己没有方法的数据类型。相反,使用它们的代码会使用pattern matching上的algebraic data types来检查它们的构造方式。据我所知,最接近Java的具有模式匹配的语言是Scala。有一篇关于Scala如何实现模式匹配的有趣文章,它比较了几种可能的方法:Matching Objects With Patterns. Burak Emir, Martin Odersky, and John Williams.

  

面向对象编程中的数据按类的层次结构组织。面向对象模式匹配的问题是如何从外部探索这种层次结构。这通常涉及按运行时类型对对象进行分类,访问其成员或确定一组对象的某些其他特征。在本文中,我们比较了六种不同的模式匹配技术:面向对象的分解,访问者,类型测试/类型转换,类型规范,案例类和提取器。这些技术在与简洁性,可维护性和性能相关的九个标准上进行了比较。本文介绍了案例类和提取器作为两种新的模式匹配方法,并表明它们的组合适用于所有既定标准。

总结:在OOP中,您可以轻松修改数据类型(如添加子类),但添加新函数(方法)需要对许多类进行更改。使用ADT可以轻松添加新功能,但修改数据类型需要修改许多功能。

答案 3 :(得分:2)

instanceof的问题在于您可能会受到未来对象层次结构更改的影响。更好的方法是使用Strategy Pattern来处理您可能使用instanceof的情况。使用instanceof制作解决方案正在陷入问题战略正试图解决:到很多ifs 。有些人建立了一个社区。 Anti-IF Campaign可能是一个笑话,但是非常严肃。从长远来看,维持10-20级if-else-if的项目可能会很痛苦。在您的情况下,您最好为阵列的所有对象创建一个公共接口,并通过接口为所有对象实现setUITweenManager

interface TweenManagerAware{
   setUITweenManager(UITweenManager manager);
}

答案 4 :(得分:1)

在同一个Collection中混合不同类的对象对我来说总是有点“腥”。是否有可能/有意义将单个GameObjects集合分成多个集合,一个仅仅是游戏对象,另一个是UITweenables? (例如,使用由类键控的MultiMap)。那么你可以这样:

for (UITweenable uit : myMap.get(UITweenable.class)) {
  uit.setUITweenManager(mUITweenManager);
}

现在,当您插入地图时仍然需要instanceof,但它更好地封装 - 隐藏在不需要知道这些细节的客户端代码中

P.S。我不是所有SW“规则”的狂热分子,而是Google“Liskov Substitution Principle”。

答案 5 :(得分:0)

您可以使用不执行任何操作的实现在setUITweenManager中声明GameObject

您可以创建一个方法,为UITweenable个实例的数组中的所有GameObject个实例返回迭代器。

还有其他方法可以有效地隐藏某些抽象中的调度;例如访客或适配器模式。


  

...我是否已经在我使用instanceof这一行的某个地方犯了一些OOP的主要罪行?

不是真的(IMO)。

instanceof的最大问题是当您开始使用它来测试实现类时。特别糟糕的原因是它很难添加额外的类等等。这里instanceof UITweenable的东西似乎没有引入这个问题,因为UITweenable似乎对设计更为重要。


当你做出这些判断时,最好理解为什么(据称)糟糕的构造或用法声称是坏的。然后你看看你的具体用例,并弥补这些原因是否适用,以及你所看到的是否真的更好用于你的用例。

答案 6 :(得分:0)

您可以在需要对所有游戏对象执行某些操作时使用mGameObjects容器,并仅为GameGroup对象保留单独的容器。

这将使用更多的内存,当你添加/删除对象时,你必须更新两个容器,但它不应该是一个明显的开销,它可以让你非常有效地循环遍历所有对象。

答案 7 :(得分:0)

这种方法的问题在于它通常不会出现在您的代码中的某个地方,从而使得将来添加该接口的其他实现或多或少变得痛苦。是否避免它取决于您的考虑。有时YAGNI可以应用,这是最直接的方式。

其他人已提出替代方案,例如访客模式。

答案 8 :(得分:0)

我还有另一种方法可以避免instanceof

除非您使用的是通用工厂,否则在您创建GameObject时,您知道它是什么具体类型。所以你可以做的是传递你创建一个可观察对象的任何GameGroup,并允许它们为它添加监听器。它会像这样工作:

public class Game {
    private void makeAGameGroup() {
        mGameObjects.add(new GameGroup(mUITweenManagerInformer));
    }

    private void allocateUITweenManager() {
        mUITweenManagerInformer.fire(mUITweenManager);
    }

    private class OurUITweenManagerInformer extends UITweenManagerInformer {
        private ArrayList<UITweenManagerListener> listeners;

        public void addUITweenManagerListener(UITweenManagerListener l) {
            listeners.add(l);
        }

        public void fire(UITweenManager next) {
            for (UITweenManagerListener l : listeners)
                l.changed(next);
        }
    }
    private OurUITweenManagerInformer mUITweenManagerInformer = new OurUITweenManagerInformer();
}

public interface UITweenManagerInformer {
    public void addUITweenManagerListener(UITweenManagerListener l);
}

public interface UITweenManagerListener {
    public void changed(UITweenManager next);
}

让我了解这个解决方案的是:

  • 因为UITweenManagerInformerGameGoup的构造函数参数,所以你不能忘记将它传递给一个,而使用实例方法你可能忘记调用它。

  • 对我来说直观的是,对象需要的信息(如GameGroup需要了解当前UITweenManager的方式)应该作为构造函数参数传递 - 我喜欢将这些视为现有对象的先决条件。如果您不了解当前的UITweenManager,则不应创建GameGroup,此解决方案会强制执行此操作。

  • 永远不会使用instanceof