web-dev-qa-db-ja.com

再帰的なデータ型のinstanceofを回避する

Scalaで用語を表す単純なクラス階層を作成しました。用語は再帰的なデータ型です。たとえば、SumMultiplicationは左側(lhs)で構成されます。これは用語であり、右側は-side(rhs)、これも用語です。

Scalaのパターンマッチングにより、Termが特定の構造を持っているかどうかを簡単に確認できます。たとえば、次のメソッドは、用語がt1 * (t2 + t3)として構成されている場合、Termからt3を返します。

def fetchT3(t: Term): Option[Term] = Some(t).collect {
  case Multiplication(t1, Sum(t2, t3)) => t3
}

同じことをJavaで記述したいのですが、パターンマッチングがないために難しくなっています。眉をひそめた場合のinstanceof演算子の使用法ですが、本当の選択肢は見つかりませんでした。これが私がこれまでに思いついたものです:

public static <T> Optional<T> maybeAs(final Object obj, final Class<T> clazz) {
    try {
        return Optional.of(clazz.cast(obj));
    } catch (ClassCastException exc) {
        return Optional.empty();
    }
}

上記のfetchT3メソッドは、次のように記述できます。

public Optional<Term> fetchT3(final Term t) {
    return maybeAs(t, Multiplication.class).flatMap(mult ->
            maybeAs(mult.rhs, Sum.class).map(sum ->
                    sum.rhs
            )
    );
}

このアプローチについて私が嫌いなのは、最初に、本当に例外的ではない状況で例外を使用していることです。次に、用語が深くネストされていると読みにくくなります。例外の使用は、クラスごとに1つのメソッド(maybeAsSummaybeAsMultiplication、…)を記述し、次にinstanceofを使用することで回避できます。これにより、一方でコードの冗長性が導入されます。いずれにせよ、クラスをキャストすることは通常、Javaのデザインが貧弱であることの症状と見なされますが、私はそれを回避する方法を知りません。

だから私の質問は、これをクラスキャストの許容できる使用法の例として考えますか、それとも用語構造をチェックするためのより良い方法を見つけますか?

5
helios35

Scalaを見てからしばらく時間が経過しているため、ここでポイントを逃した場合は謝罪しますが、instanceofを実行するためにできること(セクシーではない)の1つは次のようにすべての種類の用語を列挙する列挙型を作成するには:

public enum Kind
{
    MULTIPLICATION, SUM; // etc.
}

そして、あなたのTermクラス/インターフェースで:

Kind kind();

次に、switchステートメントまたは==を使用して、用語のタイプを判別できます。この場合、instanceofは適切であるように思われますが、instanceofが他の用語の特殊化である用語を使用している場合は特にそうです。

instanceofのパフォーマンスは問題ではありません。これは、JVM IIRCの最速の操作の1つです。オブジェクトからクラスを取得して比較するよりも、高速ではないに違いありません。推奨されない理由は、ポリモーフィズムの代わりに、ポリモーフィズムを取得しなかった人や、推移性を損なう同等の実装で使用されることが多かったためです。これはいくつかの厄介なものをもたらしました。

2
JimmyJames

用語の構造を比較する方法を実装できます。このメソッドは、サブタームと再帰的に比較します。

static bool BothNull(Term a, Term b) { return a==null && b==null;}
static bool BothNotNull(Term a, Term b) { return a!=null && b!=null;}

class Term {

    Term rhs;
    Term lhs;
    Object value;

    Term(Object value){ this.value = value }

    bool HasEqualStructure(Term otherTerm) {
        bool sameType = this.GetType() == otherTerm.GetType();

        bool sameRhsType = BothNull(rhs, otherTerm.rhs)
                           || BothNotNull(rhs, otherTerm.rhs)
                              && rhs.HasEqualStructure(otherTerm.rhs);

        bool sameLhsType = BothNull(lhs, otherTerm.lhs)
                           || BothNotNull(lhs, otherTerm.lhs)
                              && lhs.HasEqualStructure(otherTerm.lhs);

        return sameType && sameRhsType && sameLhsType;
    }

    bool CopyValues(Term otherTerm) {
        if (HasEqualStructure(this, otherTerm)) {
            this.value = otherTerm.value;
            CopyValues(rhs, otherTerm.rhs);
            CopyValues(lhs, otherTerm.lhs);
            return true;
        }
        else
        {
            return false;
        }
    }
}

次に、非常に具体的な構造を確認するために、必要な構造で新しいTermインスタンスを作成し、このメソッドに渡します。このメソッドは値を無視し、用語のタイプのみを比較します。

これはあなたのケースでうまくいきますか?

編集:上記のコードを編集して、構造からデータを取得することもできます。

クエリは次のように実装できます。

//t1 * (t2 + t3)
Sum s = new Sum(new Term(23), new Term(89));
Multiplication m = new Multiplication(new Term(123), s);

Term queryTerm = m;
Object t3Pointer = s.rhs;

Term actualRuntimeTerm = SomeActualTermOfYourApp();

if (queryTerm.CopyValues(actualRuntimeTerm))
    Console.WriteLine("T3 = {0}", t3Pointer);
}
1
Emerson Cardoso