避免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和更多的代码也可以解决这个问题。 但是,“实例”似乎是比较可读和可维护的。 有什么简单但不臭的?

注意2010年10月10日添加:

事实certificate,未来可能会添加新的子类,我现有的代码将不得不妥善处理它们。 Class中的HashMap在这种情况下不起作用,因为Class不会被find。 如果从最具体和最普遍的结尾开始的一系列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 } 

您可能对Steve Yegge的亚马逊博客的这篇文章感兴趣: “多态性失败时” 。 从本质上讲,他正在解决像这样的情况,当多态性比解决问题更麻烦。

问题是要使用多态,你必须使“处理”每个“切换”类的一部分的逻辑 – 即整数等在这种情况下。 显然这是不实际的。 有时甚至在逻辑上不是放置代码的正确位置。 他build议'实例'方法是几个弊端中较小的一个。

就像所有你不得不写臭的代码的情况一样,要用一种方法(或者最多一个类)来保存,这样气味才不会泄漏出去。

正如评论中强调的那样,访问者模式将是一个不错的select。 但是,如果不直接控制目标/受让人/受访者,则无法实施该模式。 这里有一种访问者模式可能仍然可以在这里使用,即使你没有使用包装器直接控制子类(以Integer为例):

 public class IntegerWrapper { private Integer integer; public IntegerWrapper(Integer anInteger){ integer = anInteger; } //Access the integer directly such as public Integer getInteger() { return integer; } //or method passthrough... public int intValue() { return integer.intValue(); } //then implement your visitor: public void accept(NumericVisitor visitor) { visitor.visit(this); } } 

当然,包装最后一堂课可能会被认为是自己的气味,但也许是适合你的子类。 就个人而言,我不认为instanceof是一种不好的气味,尤其是如果它局限于一种方法,我会愉快地使用它(可能超过我自己的build议)。 正如你所说,它的可读性好,types安全且可维护。 一如既往,保持简单。

你可以把你处理的实例放在一个map(key:class,value:handler)中,而不是一个巨大的if

如果按键查找返回null ,则调用一个特殊的处理程序方法,尝试查找匹配的处理程序(例如,通过在地图中的每个键上调用isInstance() )。

当find一个处理程序时,将其注册到新的密钥下。

这使得一般情况下快速和简单,并允许您处理inheritance。

你可以使用reflection:

 public final class Handler { public static void handle(Object o) { try { Method handler = Handler.class.getMethod("handle", o.getClass()); handler.invoke(null, o); } catch (Exception e) { throw new RuntimeException(e); } } public static void handle(Integer num) { /* ... */ } public static void handle(BigDecimal num) { /* ... */ } // to handle new types, just add more handle methods... } 

你可以扩展这个想法来一般地处理实现某些接口的子类和类。

我认为最好的解决scheme是HashMap的Class作为键和Handler作为值。 注意,基于HashMap的解决scheme运行在恒定的algorithm复杂度θ(1),而if-instanceof-else的嗅探链运行在线性algorithm复杂度O(N)中,其中N是if- instanceof-else链中的链接数(即要处理的不同类别的数量)。 所以基于HashMap的解决scheme的性能比if-instanceof-else链路解决scheme的性能提高了N倍。 考虑你需要以不同的方式处理Message类的不同后代:Message1,Message2等。 以下是基于HashMap处理的代码片段。

 public class YourClass { private class Handler { public void go(Message message) { // the default implementation just notifies that it doesn't handle the message System.out.println( "Possibly due to a typo, empty handler is set to handle message of type %s : %s", message.getClass().toString(), message.toString()); } } private Map<Class<? extends Message>, Handler> messageHandling = new HashMap<Class<? extends Message>, Handler>(); // Constructor of your class is a place to initialize the message handling mechanism public YourClass() { messageHandling.put(Message1.class, new Handler() { public void go(Message message) { //TODO: IMPLEMENT HERE SOMETHING APPROPRIATE FOR Message1 } }); messageHandling.put(Message2.class, new Handler() { public void go(Message message) { //TODO: IMPLEMENT HERE SOMETHING APPROPRIATE FOR Message2 } }); // etc. for Message3, etc. } // The method in which you receive a variable of base class Message, but you need to // handle it in accordance to of what derived type that instance is public handleMessage(Message message) { Handler handler = messageHandling.get(message.getClass()); if (handler == null) { System.out.println( "Don't know how to handle message of type %s : %s", message.getClass().toString(), message.toString()); } else { handler.go(message); } } } 

有关在Java中使用typesvariables的更多信息: http : //docs.oracle.com/javase/tutorial/reflect/class/classNew.html

你可以考虑责任链模式 。 对于你的第一个例子,如下所示:

 public abstract class StuffHandler { private StuffHandler next; public final boolean handle(Object o) { boolean handled = doHandle(o); if (handled) { return true; } else if (next == null) { return false; } else { return next.handle(o); } } public void setNext(StuffHandler next) { this.next = next; } protected abstract boolean doHandle(Object o); } public class IntegerHandler extends StuffHandler { @Override protected boolean doHandle(Object o) { if (!o instanceof Integer) { return false; } NumberHandler.handle((Integer) o); return true; } } 

然后类似的为你的其他处理程序。 那么就是按照顺序把StuffHandlers串在一起(最具体到最不特定的,最后一个'fallback'处理器),你的firstHandler.handle(o);代码只是firstHandler.handle(o);

(另一种方法是,而不是使用一个链,只需要在你的调度器类中有一个List<StuffHandler> ,并通过列表循环,直到handle()返回true。

只要去instanceof。 所有的解决方法似乎更复杂。 这是一个博客文章,谈论它: http : //www.velocityreviews.com/forums/t302491-instanceof-not-always-bad-the-instanceof-myth.html

我已经使用reflection解决了这个问题(大约在15年前的generics时代)。

 GenericClass object = (GenericClass) Class.forName(specificClassName).newInstance(); 

我定义了一个generics类(抽象基类)。 我已经定义了许多基类的具体实现。 每个具体类都将以className作为参数加载。 这个类名被定义为configuration的一部分。

基类定义了所有具体类的通用状态,具体类将通过​​覆盖基类中定义的抽象规则来修改状态。

那时候,我不知道这个机制的名字,这个名字就是reflection

本文列出了几个备选scheme: Mapenum除了反思。