17

我在大学的某个阶段被告知(并且随后在十几岁的地方阅读)使用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 的主要罪行?

提前致谢!

4

9 回答 9

5

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);
   }
}
于 2013-03-17T05:20:45.703 回答
4

编辑

您可以在这里做两件主要的事情来让自己摆脱这种特定的instanceof. (双关语?)

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

  2. 将您正在迭代的对象的范围缩小到只熟悉目标方法的对象。我认为这是更理想的方法,但可能不适用于您当前的代码形式。

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

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

/编辑

就个人而言,我认为这不是使用instanceof. 在我看来,您可以利用一些多态性来实现您的目标。

你有没有考虑过制作setUITweenManager(...)一个方法GameObject?这样做有意义吗?

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

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

这是实际的多态性,但我不确定这是否是您当前情况的最佳方法。Collection如果可能的话,迭代UITweenable对象会更有意义。

于 2013-03-17T04:53:53.983 回答
2

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

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

请注意,其他语言(特别是函数式语言)使用不同的原则。它们不是让对象“知道”它们如何执行每个可能的操作,而是声明自己没有方法的数据类型。相反,使用它们的代码检查它们是如何使用代数数据类型上的模式匹配构造的。据我所知,与 Java 最接近的具有模式匹配的语言是Scala。有一篇关于 Scala 如何实现模式匹配的有趣论文,其中比较了几种可能的方法:Matching Objects With Patterns。布拉克·埃米尔、马丁·奥德斯基和约翰·威廉姆斯。

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

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

于 2013-03-17T08:34:51.283 回答
2

instanceof 的问题在于,您可能会受到未来对象层次结构更改的影响。更好的方法是在您可能使用 instanceof 的情况下使用策略模式。用 instanceof 解决问题,你陷入了一个问题 Strategy 试图解决的问题:to many ifs。有些人建立了一个社区。Anti-IF Campaign可能是个笑话,但 untipattern 是严肃的。从长远来看,维持 10-20 级 if-else-if 的项目可能会很痛苦。在您的情况下,您最好为数组的所有对象创建一个通用接口,setUITweenManager并通过接口为所有对象实现。

interface TweenManagerAware{
   setUITweenManager(UITweenManager manager);
}
于 2013-03-17T17:38:13.953 回答
1

在同一个集合中混合不同类的对象对我来说总是有点“可疑”。将单个游戏对象集合拆分为多个集合,其中一个是游戏对象,另一个是 UITweenables,是否有可能/有意义?(例如,使用由类键入的 MultiMap)。然后你可以这样做:

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

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

ps 我不是对所有的 SW“规则”,而是对谷歌“Liskov 替换原则”的狂热。

于 2013-03-17T05:12:51.300 回答
0

You could use the mGameObjects container for when you need to do something on all game objects and keep a separate container only for GameGroup objects.

This will use some more memory, and when you add/remove objects you have to update both containers, but it shouldn't be a noticeable overhead, and it lets you loop very efficiently through all the objects.

于 2013-03-17T06:51:23.060 回答
0

这种方法的问题在于,它通常不会只出现在您的代码中的一个地方,因此在将来添加另一个接口实现时或多或少会很痛苦。是否避免它取决于您的考虑。有时可以应用 YAGNI,这是最直接的方式。

其他人提出了替代方案,例如访问者模式。

于 2013-03-17T08:59:24.773 回答
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);
}

这个解决方案吸引我的是:

  • 因为 aUITweenManagerInformer是 的构造函数参数GameGoup,所以您不能忘记将其传递给它,而对于实例方法,您可能会忘记调用它。

  • GameGroup对我来说,一个对象需要的信息(比如需要知道 current的方式UITweenManager)应该作为构造函数参数传递,这对我来说很直观——我喜欢将这些视为对象存在的先决条件。如果您不了解 current UITweenManager,则不应创建GameGroup,并且此解决方案强制执行此操作。

  • instanceof从未使用过。

于 2013-03-23T15:36:35.000 回答
0

您可以使用什么都不做的实现setUITweenManager来声明。GameObject

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

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


...我是否在我在这里使用的某处犯了一些 OOP 的主要罪行instanceof

不是真的(海事组织)。

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


当你做出这些判断时,最好理解为什么(据说)糟糕的构造或用法被认为是糟糕的原因。然后,您查看您的特定用例并弥补这些原因是否适用,以及您正在查看的替代方案在您的用例中是否真的更好。

于 2013-03-17T05:30:34.477 回答