30

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

我希望两个 RecordType 对象都由“进程”方法中的同一个 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 类,其中定义了最少的状态更改方法。
4

6 回答 6

31

访问者模式通常用于这种情况。虽然代码有点复杂,但是在添加一个新的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而不是在没有找到任何类型时返回。

于 2012-01-12T20:11:38.217 回答
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

于 2012-01-12T20:16:31.763 回答
0

另一种可能的方法是使 process() (或者如果可以澄清事情,可以将其称为“doSubclassProcess()”)抽象(在 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;
   }
}

注意几个错别字——我想我把它们都改好了。

于 2012-01-12T20:23:17.753 回答
0

设计是达到目的的一种手段,在不知道你的目标或约束的情况下,没有人能判断你的设计在那种特定情况下是否优秀,或者如何改进它。

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

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

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

于 2012-01-12T20:26:08.550 回答
0

一个想法是糟糕的设计,就像在你的例子中一样,在适用的情况下不使用访问者模式。

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

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

于 2012-01-12T22:26:30.490 回答
0

违背了SOLID的开闭原则

于 2020-01-26T19:15:36.430 回答