web-dev-qa-db-ja.com

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などを管理しています。

数行でできるような多くのコード行を使いたくありません。 (時々、Integer.classをIntegerStuffのインスタンスに、BigDecimal.classをBigDecimalStuffのインスタンスにマッピングするHashMapを作成します。しかし、今日はもっとシンプルにしたいです。)

私はこれほど簡単なものが欲しいです:

public static handle(Integer num) { ... }
public static handle(BigDecimal num) { ... }

しかし、Javaはそのようには動作しません。

フォーマット時に静的メソッドを使用したいと思います。私がフォーマットしているものは複合です。Thing1にはThing2の配列を含めることができ、Thing2にはThing1の配列を含めることができます。このようなフォーマッタを実装すると問題が発生しました。

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を知っていますが、もう少しコードを修正することもできます。しかし、「instanceof」は、比較すると読みやすく、保守しやすいようです。シンプルだが臭いがないものはありますか?

2010年5月10日に追加された注:

おそらく新しいサブクラスが将来追加される可能性があり、私の既存のコードはそれらを適切に処理する必要があります。その場合、クラスが見つからないため、クラスのHashMapは機能しません。結局、最も具体的なものから始まり、最も一般的なもので終わる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
}
95
Mark Lutton

Steve YeggeのAmazonブログのこのエントリに興味があるかもしれません: "when polymorphism failed" 。基本的に彼は、ポリモーフィズムが解決するよりも多くの問題を引き起こすこのようなケースに取り組んでいます。

問題は、ポリモーフィズムを使用するには、各「切り替え」クラスの「ハンドル」部分のロジックを作成する必要があることです。つまり、この場合は整数などです。これは明らかに実用的ではありません。コードを配置するのに論理的にも適切な場所ではない場合があります。彼は、「instanceof」アプローチをいくつかの悪の少ない方として推奨しています。

臭いコードを書くことを余儀なくされるすべての場合と同様に、臭いが漏れないように、1つのメソッド(または多くても1つのクラス)でボタンを締めておいてください。

51
DJClayworth

コメントで強調されているように、訪問者パターンは良い選択です。ただし、ターゲット/アクセプター/訪問者を直接制御しないと、そのパターンを実装できません。ラッパーを使用してサブクラスを直接制御できない場合でも、ビジターパターンをここで使用できる方法の1つを次に示します(例として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がここの悪臭だとは思いません。特にそれが1つの方法に限定されていて、喜んでそれを使用する場合は(おそらく上記の私自身の提案より)。あなたが言うように、それは非常に読みやすく、タイプセーフであり、保守可能である。いつものように、シンプルにしてください。

20
Chris Knight

巨大なifの代わりに、処理するインスタンスをマップに配置できます(キー:クラス、値:ハンドラー)。

キーによるルックアップがnullを返す場合、一致するハンドラーの検索を試みる特別なハンドラーメソッドを呼び出します(たとえば、マップ内のすべてのキーでisInstance()を呼び出します)。

ハンドラーが見つかったら、新しいキーの下に登録します。

これにより、一般的なケースが迅速かつ簡単になり、継承を処理できるようになります。

15
Aaron Digulla

リフレクションを使用できます:

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

アイデアを拡張して、特定のインターフェイスを実装するサブクラスとクラスを一般的に処理できます。

13
Jordão

Chain of Responsibilityパターン を検討できます。最初の例では、次のようなものです:

_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を順番にストリング化する場合(最終的に「フォールバック」ハンドラーを使用して、最も具体的で最も具体的ではない)で、ディスパッチャーコードはfirstHandler.handle(o);です。

(代替方法は、チェーンを使用するのではなく、ディスパッチャクラスに_List<StuffHandler>_を保持し、handle()がtrueを返すまでリストをループすることです)。

9
Cowan

クラスとしてのキーと値としてのハンドラーを備えたHashMapが最良のソリューションだと思います。 HashMapベースのソリューションは一定のアルゴリズムの複雑度θ(1)で実行され、if-instanceof-elseの匂いがするチェーンは線形のアルゴリズムの複雑度O(N)で実行されます。Nはif-instanceof-elseチェーンのリンクの数(つまり、処理する異なるクラスの数)。したがって、HashMapベースのソリューションのパフォーマンスは、if-instanceof-elseチェーンソリューションのパフォーマンスよりもN倍に漸近的に高くなります。 Message1、Message2など、Messageクラスの異なる子孫を別々に処理する必要があることを考慮してください。以下は、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でのクラス型の変数の使用に関する詳細: http://docs.Oracle.com/javase/tutorial/reflect/class/classNew.html

9
Serge Rogatch

Instanceofを使用します。すべての回避策はより複雑に見えます。これについてのブログ投稿は次のとおりです。 http://www.velocityreviews.com/forums/t302491-instanceof-not-always-bad-the-instanceof-myth.html

6
Jose Martinez

私はreflectionを使用してこの問題を解決しました(ジェネリック以前の時代の約15年前)。

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

1つの汎用クラス(抽象基本クラス)を定義しました。基本クラスの多くの具体的な実装を定義しました。各具象クラスは、パラメータとしてclassNameでロードされます。このクラス名は、構成の一部として定義されます。

基本クラスはすべての具象クラスに共通の状態を定義し、具象クラスは基本クラスで定義された抽象ルールをオーバーライドすることにより状態を変更します。

当時、私はreflectionとして知られているこのメカニズムの名前を知りません。

これにリストされている他の選択肢はほとんどありません 記事Mapおよびenumは反射を除きます。

0
Ravindra babu