是否使用" instanceof"运营商认为糟糕的设计?

时间:2012-01-12 20:08:18

标签: java design-patterns instanceof object-oriented-analysis

在我的一个项目中,我有两个“数据传输对象”RecordType1和RecordType2,它们继承自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 ++ 中写下以下内容:

“任何时候你发现你自己编写的形式代码'如果对象属于T1类型,那么就做一些事情,但是如果它是T2类型,那么别做其他事情,'自己拍吧。”

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

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

更新

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

6 个答案:

答案 0 :(得分:26)

Visitor模式通常用于此类情况。虽然代码有点复杂,但是在添加新的RecordType子类后,必须在任何地方实现逻辑,否则将无法编译。只要instanceof遍布整个地方,就很容易错过一两个地方。

示例:

public abstract class RecordType {
    public abstract <T> T accept(RecordTypeVisitor<T> visitor);
}

public interface RecordTypeVisitor<T> {
    T visitOne(RecordType1 recordType);
    T visitTwo(RecordType2 recordType);
}

public class RecordType1 extends RecordType {
    public <T> T accept(RecordTypeVisitor<T> visitor) {
        return visitor.visitOne(this);
    }
}

public class RecordType2 extends RecordType {
    public <T> T accept(RecordTypeVisitor<T> visitor) {
        return visitor.visitTwo(this);
    }
}

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

String result = record.accept(new RecordTypeVisitor<String>() {

    String visitOne(RecordType1 recordType) {
        //processing of RecordType1
        return "Jeden";
    }

    String visitTwo(RecordType2 recordType) {
        //processing of RecordType2
        return "Dwa";
    }

});

我也建议抛出异常:

throw new IllegalArgumentException(record);

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

答案 1 :(得分:3)

我的建议:

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

答案 2 :(得分:0)

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

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;
   }
}

注意一些错别字 - 想想我已经解决了所有问题。

答案 3 :(得分:0)

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

但是,在面向对象的设计中,将方法实现保留在单独的类中的标准方法是visitor pattern

仍为每种类型提供单独的实现。

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??"

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

答案 4 :(得分:0)

如果您的示例中有错误的设计,则在适用时不使用访客模式。

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

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

答案 5 :(得分:0)

违反SOLID的开闭原则