这种“instanceof”运算符的使用是否被认为是错误的设计?

在我的一个项目中,我有两个“数据传输对象”RecordType1和RecordType2,它们inheritance自RecordType的抽象类。

我希望两个RecordType对象在“process”方法中由同一个RecordProcessor类处理。 我的第一个想法是创建一个通用的流程方法,该方法委托给两个特定的流程方法,如下所示:

public RecordType process(RecordType record){ if (record instanceof RecordType1) return process((RecordType1) record); else if (record instanceof RecordType2) return process((RecordType2) record); throw new IllegalArgumentException(record); } public RecordType1 process(RecordType1 record){ // Specific processing for Record Type 1 } public RecordType2 process(RecordType2 record){ // Specific processing for Record Type 2 } 

我读过Scott Meyers在Effective C ++中写了以下内容:

“任何时候你发现你自己编写的forms代码’如果对象是T1类型,那么做一些事情,但如果它是T2类型,那么做点其他事情,’打自己吧。”

如果他是正确的,显然我应该打自己。 我真的没有看到这是多么糟糕的设计(除非当然有人将RecordType子类化并添加到RecordType3而不向处理它的通用“Process”方法添加另一行,从而创建一个NPE),以及我能想到的替代方案涉及将特定处理逻辑首当其冲地放在RecordType类本身中,这对我来说真的没有多大意义,因为理论上我可以对这些记录执行许多不同类型的处理。

有人可以解释为什么这可能被视为糟糕的设计并提供某种替代方案,仍然负责将这些记录处理到“处理”类?

更新:

  • 更改return nullthrow new IllegalArgumentException(record);
  • 只是为了澄清,简单的RecordType.process()方法不足以解决三个原因:首先,处理实际上远离RecordType,在RecordType子类中应该拥有自己的方法。 此外,理论上可以由不同的处理器执行大量不同类型的处理。 最后,RecordType被设计为一个简单的DTO类,其中定义了最小的状态改变方法。

访客模式通常用于此类情况。 虽然代码有点复杂,但是在添加新的RecordType子类之后,你必须在任何地方实现逻辑,因为它不会编译。 随着整个地方的instanceof ,很容易错过一两个地方。

例:

 public abstract class RecordType { public abstract  T accept(RecordTypeVisitor visitor); } public interface RecordTypeVisitor { T visitOne(RecordType1 recordType); T visitTwo(RecordType2 recordType); } public class RecordType1 extends RecordType { public  T accept(RecordTypeVisitor visitor) { return visitor.visitOne(this); } } public class RecordType2 extends RecordType { public  T accept(RecordTypeVisitor visitor) { return visitor.visitTwo(this); } } 

用法(注意generics返回类型):

 String result = record.accept(new RecordTypeVisitor() { String visitOne(RecordType1 recordType) { //processing of RecordType1 return "Jeden"; } String visitTwo(RecordType2 recordType) { //processing of RecordType2 return "Dwa"; } }); 

另外我建议抛出exception:

 throw new IllegalArgumentException(record); 

而不是在找不到任何类型时返回null

我的建议:

 public RecordType process(RecordType record){ return record.process(); } public class RecordType { public RecordType process() { return null; } } public class RecordType1 extends RecordType { @Override public RecordType process() { ... } } public class RecordType2 extends RecordType { @Override public RecordType process() { ... } } 

如果您需要执行的代码与模型不应该知道的内容(如UI)相关联,那么您将需要使用一种双重调度或访问者模式。

http://en.wikipedia.org/wiki/Double_dispatch

另一种可能的方法是使process()(或者可能称之为“doSubclassProcess()”,如果它澄清事物)abstract(在RecordType中),并在子类中具有实际的实现。 例如

 class RecordType { protected abstract RecordType doSubclassProcess(RecordType rt); public process(RecordType rt) { // you can do any prelim or common processing here // ... // now do subclass specific stuff... return doSubclassProcess(rt); } } class RecordType1 extends RecordType { protected RecordType1 doSubclassProcess(RecordType RT) { // need a cast, but you are pretty sure it is safe here RecordType1 rt1 = (RecordType1) rt; // now do what you want to rt return rt1; } } 

注意一些错别字 – 想想我把它们全部修好了。

设计是达到目的的手段,并且不了解您的目标或约束,没有人能够判断您的设计在特定情况下是否良好,或者如何改进。

但是,在面向对象的设计中,将方法实现保持在单独的类中的标准方法是访问者模式,同时仍然为每种类型提供单独的实现。

PS:在代码审查中,我标记return null ,因为它可能传播错误而不是报告它们。 考虑:

 RecordType processed = process(new RecordType3()); // many hours later, in a different part of the program processed.getX(); // "Why is this null sometimes??" 

换句话说,所谓的无法访问的代码路径应该抛出exception,而不是导致未定义的行为。

一个人认为不好的设计,如你的例子,在适用时不使用访客模式。

另一个是效率 。 与其他技术相比, instanceof相当慢,例如使用相等性来比较class对象。

使用访问者模式时,通常一个有效而优雅的解决方案是使用Map在支持class访问者实例之间进行映射。 使用instanceof检查的大if ... else块将非常无效。