这是“instanceof”的使用吗?操作员认为设计不好?

发布于 2024-12-26 10:01:47 字数 1382 浏览 1 评论 0原文

在我的一个项目中,我有两个“数据传输对象”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更改为抛出新的IllegalArgumentException(record);
  • 只是为了澄清一下,简单的RecordType.process()方法有三个原因还不够:首先,处理实际上与 RecordType 相去甚远,不值得在 RecordType 子类中拥有自己的方法。此外,理论上可以由不同的处理器执行大量不同类型的处理。最后,RecordType 被设计为一个简单的 DTO 类,其中定义了最少的状态更改方法。

In one of my projects, I have two "data transfer objects" RecordType1 and RecordType2 that inherit from an abstract class of RecordType.

I want both RecordType objects to be processed by the same RecordProcessor class within a "process" method. My first thought was to create a generic process method that delegates to two specific process methods as follows:

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
}

I've read that Scott Meyers writes the following in Effective C++ :

"Anytime you find yourself writing code of the form 'if the object is of type T1, then do something, but if it's of type T2, then do something else,' slap yourself."

If he's correct, clearly I should be slapping myself. I don't really see how this is bad design (unless of course somebody subclasses RecordType and adds in a RecordType3 without adding another line to the generic "Process" method that handles it, thus creating a NPE), and the alternatives I can think of involve putting the brunt of the specific processing logic within the RecordType classes themselves, which really doesn't make much sense to me since there can in theory be many different types of processing I'd like to perform on these records.

Can someone explain why this might be considered bad design and provide some sort of alternative that still gives the responsibility for processing these records to a "Processing" class?

UPDATE:

  • Changed return null to throw new IllegalArgumentException(record);
  • Just to clarify, there are three reasons a simple RecordType.process() method would not suffice: First, the processing is really too far removed from RecordType to deserve its own method in the RecordType subclasses. Also, there are a whole slew of different types of processing that could theoretically be performed by different processors. Finally, RecordType is designed to be a simple DTO class with minimal state-changing methods defined within.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

追星践月 2025-01-02 10:01:47

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

The Visitor pattern is typically used in such cases. Although the code is a bit more complicated, but after adding a new RecordType subclass you have to implement the logic everywhere, as it won't compile otherwise. With instanceof all over the place it is very easy to miss one or two places.

Example:

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

Usage (note the generic return type):

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

});

Also I would recommend throwing an exception:

throw new IllegalArgumentException(record);

instead of returning null when neither type is found.

放低过去 2025-01-02 10:01:47

我的建议:

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

My suggestion:

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()
    {
        ...
    }
}

If the code you need to execute is coupled to something the model shouldn't know (like UI) then you will need to use a kind of double dispatch or visitor pattern.

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

自控 2025-01-02 10:01:47

另一种可能的方法是使 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;
   }
}

注意一些拼写错误——我想我已经把它们全部改正了。

Another possible approach would be to make process() (or perhaps call it "doSubclassProcess()" if that clarifies things) abstract (in RecordType), and have the actual implementations in the subclasses. 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;
   }
}

Watch out for a couple of typos - think I fixed them all.

冰雪梦之恋 2025-01-02 10:01:47

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

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

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

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

Design is a means to an end, and not knowing your goal or constraints, nobody can tell whether your design is good in that particular situation, or how it might be improved.

However, in object oriented design, the standard approach to keep the method implemention in a separate class while still having a separate implementation for each type is the visitor pattern.

PS: In a code review, I'd flag return null, because it might propagate bugs rather than reporting them. Consider:

RecordType processed = process(new RecordType3());

// many hours later, in a different part of the program

processed.getX(); // "Why is this null sometimes??"

Put differently, supposedly unreachable code paths should throw an exception rather than result in undefined behaviour.

尛丟丟 2025-01-02 10:01:47

一种糟糕的设计,如您的示例所示,在适用时不使用访问者模式。

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

当使用visitor模式时,通常一个有效且优雅的解决方案是使用Map在支持classVisitor实例之间进行映射。带有 instanceof 检查的大型 if ... else 块将非常低效。

Bad design in one think, as in your example, not using visitor pattern, when applicable.

Another is efficiency. instanceof is quite slow, compared to other techniques, such as comparing against class object using equality.

When using visitor pattern, usually an effective and elegant solution is using Map for mapping between support class and Visitor instance. Large if ... else block with instanceof checks would be very ineffective.

寻找我们的幸福 2025-01-02 10:01:47

违背了SOLID的开闭原则

It is against open-closed principle of SOLID

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文