我在大学的某个阶段被告知(并且随后在十几个地方读过),使用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的主要罪行?这里?
提前致谢!
答案 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
的特定实例。 (双关语?)
按照我的初步答案建议并将您要定位的方法移动到您正在迭代的类。在这种情况下,这并不理想,因为该方法对父对象没有意义,并且会像Ted所说的那样污染。
将正在迭代的对象的范围缩小为仅熟悉目标方法的对象。我认为这是更理想的方法,但在当前的代码形式中可能无法实现。
就个人而言,我避免像瘟疫一样instanceof
因为它让我觉得我完全错过了某些东西,但有时候它是必要的。如果您的代码以这种方式布局,并且您无法缩小正在迭代的对象的范围,那么instanceof
可能会正常工作。但这似乎是一个很好的机会,可以看到多态如何使您的代码在未来更容易阅读和维护。
我将在下面留下原始答案,以保持评论的完整性。
<强> /修改
就个人而言,我并不认为这是使用instanceof
的充分理由。在我看来,你可以利用一些多态来实现你的目标。
您是否考虑过将setUITweenManager(...)
作为GameObject
的方法?这样做有意义吗?
如果它确实有意义,您可以让默认实现不执行任何操作,并让GameGroup
覆盖该方法以执行您希望它执行的操作。此时,您的代码可能就像这样:
private void allocateUITweenManager() {
for(GameObject go:mGameObjects){
go.setUITweenManager(mUITweenManager);
}
}
这是多态性的行动,但我不确定它是否适合您当前的情况。如果可能的话,迭代Collection
个UITweenable
对象会更有意义。
答案 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);
}
让我了解这个解决方案的是:
因为UITweenManagerInformer
是GameGoup
的构造函数参数,所以你不能忘记将它传递给一个,而使用实例方法你可能忘记调用它。
对我来说直观的是,对象需要的信息(如GameGroup
需要了解当前UITweenManager
的方式)应该作为构造函数参数传递 - 我喜欢将这些视为现有对象的先决条件。如果您不了解当前的UITweenManager
,则不应创建GameGroup
,此解决方案会强制执行此操作。
永远不会使用instanceof
。