避免在Java中使用instanceof
拥有一系列“instanceof”操作被认为是“代码味道”。标准答案是“使用多态性”。在这种情况下我该怎么做?
一个基类有多个子类;他们都不在我的控制之下。类似的情况是 Java 类 Integer、Double、BigDecimal 等。
if (obj instanceof Integer) {NumberStuff.handle((Integer)obj);}
else if (obj instanceof BigDecimal) {BigDecimalStuff.handle((BigDecimal)obj);}
else if (obj instanceof Double) {DoubleStuff.handle((Double)obj);}
我确实可以控制 NumberStuff 等。
我不想使用很多行代码,而几行代码就可以了。 (有时我会创建一个 HashMap,将 Integer.class 映射到 IntegerStuff 的实例,将 BigDecimal.class 映射到 BigDecimalStuff 的实例等。但今天我想要更简单的东西。)
我想要像这样简单的东西:
public static handle(Integer num) { ... }
public static handle(BigDecimal num) { ... }
但 Java 不这样做那样工作。
我想在格式化时使用静态方法。我正在格式化的内容是复合的,其中 Thing1 可以包含 Thing2s 数组,Thing2 可以包含 Thing1s 数组。当我实现这样的格式化程序时,我遇到了一个问题:
class Thing1Formatter {
private static Thing2Formatter thing2Formatter = new Thing2Formatter();
public format(Thing thing) {
thing2Formatter.format(thing.innerThing2);
}
}
class Thing2Formatter {
private static Thing1Formatter thing1Formatter = new Thing1Formatter();
public format(Thing2 thing) {
thing1Formatter.format(thing.innerThing1);
}
}
是的,我知道 HashMap,更多的代码也可以解决这个问题。但相比之下,“instanceof”似乎更具可读性和可维护性。有没有什么简单又不臭的东西?
注:2010 年 5 月 10 日添加:
事实证明,将来可能会添加新的子类,而我现有的代码将必须妥善处理它们。在这种情况下,类上的 HashMap 将不起作用,因为找不到该类。一连串的 if 语句,从最具体的开始,到最一般的结束,可能是最好的:
if (obj instanceof SubClass1) {
// Handle all the methods and properties of SubClass1
} else if (obj instanceof SubClass2) {
// Handle all the methods and properties of SubClass2
} else if (obj instanceof Interface3) {
// Unknown class but it implements Interface3
// so handle those methods and properties
} else if (obj instanceof Interface4) {
// likewise. May want to also handle case of
// object that implements both interfaces.
} else {
// New (unknown) subclass; do what I can with the base class
}
Having a chain of "instanceof" operations is considered a "code smell". The standard answer is "use polymorphism". How would I do it in this case?
There are a number of subclasses of a base class; none of them are under my control. An analogous situation would be with the Java classes Integer, Double, BigDecimal etc.
if (obj instanceof Integer) {NumberStuff.handle((Integer)obj);}
else if (obj instanceof BigDecimal) {BigDecimalStuff.handle((BigDecimal)obj);}
else if (obj instanceof Double) {DoubleStuff.handle((Double)obj);}
I do have control over NumberStuff and so on.
I don't want to use many lines of code where a few lines would do. (Sometimes I make a HashMap mapping Integer.class to an instance of IntegerStuff, BigDecimal.class to an instance of BigDecimalStuff etc. But today I want something simpler.)
I'd like something as simple as this:
public static handle(Integer num) { ... }
public static handle(BigDecimal num) { ... }
But Java just doesn't work that way.
I'd like to use static methods when formatting. The things I'm formatting are composite, where a Thing1 can contain an array Thing2s and a Thing2 can contain an array of Thing1s. I had a problem when I implemented my formatters like this:
class Thing1Formatter {
private static Thing2Formatter thing2Formatter = new Thing2Formatter();
public format(Thing thing) {
thing2Formatter.format(thing.innerThing2);
}
}
class Thing2Formatter {
private static Thing1Formatter thing1Formatter = new Thing1Formatter();
public format(Thing2 thing) {
thing1Formatter.format(thing.innerThing1);
}
}
Yes, I know the HashMap and a bit more code can fix that too. But the "instanceof" seems so readable and maintainable by comparison. Is there anything simple but not smelly?
Note added 5/10/2010:
It turns out that new subclasses will probably be added in the future, and my existing code will have to handle them gracefully. The HashMap on Class won't work in that case because the Class won't be found. A chain of if statements, starting with the most specific and ending with the most general, is probably the best after all:
if (obj instanceof SubClass1) {
// Handle all the methods and properties of SubClass1
} else if (obj instanceof SubClass2) {
// Handle all the methods and properties of SubClass2
} else if (obj instanceof Interface3) {
// Unknown class but it implements Interface3
// so handle those methods and properties
} else if (obj instanceof Interface4) {
// likewise. May want to also handle case of
// object that implements both interfaces.
} else {
// New (unknown) subclass; do what I can with the base class
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(10)
您可能对 Steve Yegge 的 Amazon 博客中的这篇文章感兴趣:“当多态性失败时”< /a>.本质上,他正在解决这样的情况,即多态性造成的麻烦多于其解决的麻烦。
问题是,要使用多态性,您必须将“处理”逻辑作为每个“切换”类的一部分 - 即本例中的“整数”等。显然这是不切实际的。有时,从逻辑上讲,它甚至不是放置代码的正确位置。他推荐“instanceof”方法,因为它是几种弊端中较小的一个。
与所有被迫编写有气味的代码的情况一样,将其保留在一种方法(或最多一个类)中,这样气味就不会泄漏出去。
You might be interested in this entry from Steve Yegge's Amazon blog: "when polymorphism fails". Essentially he's addressing cases like this, when polymorphism causes more trouble than it solves.
The issue is that to use polymorphism you have to make the logic of "handle" part of each 'switching' class - i.e. Integer etc. in this case. Clearly this is not practical. Sometimes it isn't even logically the right place to put the code. He recommends the 'instanceof' approach as being the lesser of several evils.
As with all cases where you are forced to write smelly code, keep it buttoned up in one method (or at most one class) so that the smell doesn't leak out.
正如评论中强调的那样,访问者模式将是一个不错的选择。但如果没有对目标/接受者/访问者的直接控制,你就无法实现该模式。这是访问者模式仍然可以在这里使用的一种方式,即使您无法通过使用包装器直接控制子类(以 Integer 为例):
当然,包装最终类可能会被认为是它自己的味道,但也许它非常适合您的子类。就我个人而言,我不认为
instanceof
这里有那么难闻,特别是如果它仅限于一种方法并且我很乐意使用它(可能超过我自己的建议)。正如你所说,它具有良好的可读性、类型安全性和可维护性。一如既往,保持简单。As highlighted in the comments, the visitor pattern would be a good choice. But without direct control over the target/acceptor/visitee you can't implement that pattern. Here's one way the visitor pattern could possibly still be used here even though you have no direct control over the subclasses by using wrappers (taking Integer as an example):
Of course, wrapping a final class might be considered a smell of its own but maybe it's a good fit with your subclasses. Personally, I don't think
instanceof
is that bad a smell here, especially if it is confined to one method and I would happily use it (probably over my own suggestion above). As you say, its quite readable, typesafe and maintainable. As always, keep it simple.您可以将处理的实例放入映射中(键:类,值:处理程序),而不是使用巨大的
if
。如果按键查找返回
null
,则调用一个特殊的处理程序方法来尝试查找匹配的处理程序(例如,通过对映射中的每个键调用isInstance()
)。找到处理程序后,将其注册到新密钥下。
这使得一般情况变得快速而简单,并允许您处理继承。
Instead of a huge
if
, you can put the instances you handle in a map (key: class, value: handler).If the lookup by key returns
null
, call a special handler method which tries to find a matching handler (for example by callingisInstance()
on every key in the map).When a handler is found, register it under the new key.
This makes the general case fast and simple and allows you to handle inheritance.
您可以使用反射:
您可以扩展这个想法来一般处理实现某些接口的子类和类。
You can use reflection:
You can expand on the idea to generically handle subclasses and classes that implement certain interfaces.
我认为最好的解决方案是以 Class 为键、Handler 为值的 HashMap。请注意,基于 HashMap 的解决方案以恒定的算法复杂度 θ(1) 运行,而 if-instanceof-else 的嗅觉链以线性算法复杂度 O(N) 运行,其中 N 是 if-instanceof-else 链中的链接数(即要处理的不同类的数量)。因此,基于 HashMap 的解决方案的性能比 if-instanceof-else 链解决方案的性能渐进高 N 倍。
考虑到您需要以不同的方式处理 Message 类的不同后代:Message1、Message2 等。下面是基于 HashMap 的处理的代码片段。
有关 Java 中 Class 类型变量的使用的更多信息:http://docs .oracle.com/javase/tutorial/reflect/class/classNew.html
I think that the best solution is HashMap with Class as key and Handler as value. Note that HashMap based solution runs in constant algorithmic complexity θ(1), while the smelling chain of if-instanceof-else runs in linear algorithmic complexity O(N), where N is the number of links in the if-instanceof-else chain (i.e. the number of different classes to be handled). So the performance of HashMap based solution is asymptotically higher N times than the performance of if-instanceof-else chain solution.
Consider that you need to handle different descendants of Message class differently: Message1, Message2, etc. . Below is the code snippet for HashMap based handling.
More info on usage of variables of type Class in Java: http://docs.oracle.com/javase/tutorial/reflect/class/classNew.html
您可以考虑责任链模式。对于您的第一个示例,类似于:
然后对于其他处理程序也类似。然后是按顺序将 StuffHandler 串在一起的情况(从最具体到最不具体,带有最终的“后备”处理程序),并且您的调度程序代码只是
firstHandler.handle(o);
。(另一种方法是,不使用链,而是在调度程序类中使用
List
,并让它循环遍历列表,直到handle()
返回真的)。You could consider the Chain of Responsibility pattern. For your first example, something like:
and then similarly for your other handlers. Then it's a case of stringing together the StuffHandlers in order (most specific to least specific, with a final 'fallback' handler), and your despatcher code is just
firstHandler.handle(o);
.(An alternative is to, rather than using a chain, just have a
List<StuffHandler>
in your dispatcher class, and have it loop through the list untilhandle()
returns true).只需使用instanceof即可。所有的解决方法似乎都更加复杂。这是一篇讨论它的博客文章: http://www.velocityreviews.com/forums/t302491-instanceof-not-always-bad-the-instanceof-myth.html
Just go with the instanceof. All the workarounds seem more complicated. Here is a blog post that talks about it: http://www.velocityreviews.com/forums/t302491-instanceof-not-always-bad-the-instanceof-myth.html
我已经使用
reflection
解决了这个问题(大约 15 年前的前泛型时代)。我定义了一个通用类(抽象基类)。我已经定义了基类的许多具体实现。每个具体类都将以 className 作为参数进行加载。此类名称被定义为配置的一部分。
基类定义了所有具体类的公共状态,具体类将通过覆盖基类中定义的抽象规则来修改状态。
当时我还不知道这个机制的名字,一直被称为
反射
。这篇文章中列出了更多替代方案:
Map
和enum
除了反射之外。I have solved this problem using
reflection
(around 15 years back in pre Generics era).I have defined one Generic Class ( abstract Base class). I have defined many concrete implementations of base class. Each concrete class will be loaded with className as parameter. This class name is defined as part of configuration.
Base class defines common state across all concrete classes and concrete classes will modify the state by overriding abstract rules defined in base class.
At that time, I don't know the name of this mechanism, which has been known as
reflection
.Few more alternatives are listed in this article :
Map
andenum
apart from reflection.在 BaseClass 中添加一个返回类名称的方法。并使用特定类名覆盖方法
现在按以下方式使用 switch case-
Add a method in BaseClass which returns name of the class. And override the methods with the specific class name
Now use the switch case in following way-
我在 Java 8 中使用的是:
What I use for Java 8: