Evitando instanceof para tipos de dados recursivos

5

Eu escrevi uma hierarquia de classes simples para representar termos no Scala. Os termos são tipos de dados recursivos, por exemplo, Sum e Multiplication consistem no lado esquerdo ( lhs ), que é um Termo, e o lado direito ( rhs ), que também é um Termo.

A correspondência de padrões do Scala facilita a verificação se um Termo tem uma determinada estrutura. Por exemplo, aqui está um método que retorna t3 do Termo, se estiver estruturado como t1 * (t2 + t3) :

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

Agora, quero escrever o mesmo em Java, mas a falta de correspondência de padrões torna isso difícil. Uso do operador instanceof se desaprovado, mas não encontrei uma alternativa real. Aqui está o que eu tenho até agora:

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

O método fetchT3 acima pode ser escrito como:

public Optional<Term> fetchT3(final Term t) {
    return maybeAs(t, Multiplication.class).flatMap(mult ->
            maybeAs(mult.rhs, Sum.class).map(sum ->
                    sum.rhs
            )
    );
}
O que eu não gosto sobre esta abordagem é que, primeiro, está usando exceções para situações que não são verdadeiramente excepcionais, e segundo, vai se tornar difícil de ler quando os termos são profundamente aninhados. Usar exceções pode ser evitado escrevendo um método para cada classe ( maybeAsSum , maybeAsMultiplication ,…) e, em seguida, usando instanceof , que por outro lado introduziria redundância de código. De qualquer maneira, as aulas de elenco geralmente são vistas como um sintoma de design pobre em Java, mas não sei como contornar isso.

Então, a minha pergunta é, você consideraria isso como um exemplo de uso aceitável da transmissão de classe ou você vê um método melhor para verificar a estrutura de termos?

    
por helios35 25.08.2017 / 13:00
fonte

2 respostas

2
Tem sido um tempo desde que eu olhei para Scala, então peço desculpas se eu perder o ponto aqui, mas uma coisa que você pode fazer (que não é sexy) para fazer um instanceof é criar um tipo enum que enumera todos os tipos de termos assim:

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

E, em seguida, na sua classe / interface do termo:

Kind kind();

Você pode então usar uma instrução switch ou == para determinar o tipo do termo. Eu diria que esse exemplo parece apropriado neste caso, especialmente se você tiver Termos que são especializações de outros Termos, pois instanceof pode funcionar como você deseja para isso.

O desempenho de instanceof não é um problema. É uma das operações mais rápidas da JVM, o IIRC. Deve ser tão rápido, se não mais rápido, do que recuperar a classe de um objeto e compará-la. A razão pela qual é desencorajada é que ela foi frequentemente usada em vez de polimorfismo por aqueles que não a obtiveram e também em implementações iguais quando ela quebraria a transitividade. Isso resultou em algumas coisas desagradáveis.

    
por 25.08.2017 / 23:36
fonte
1

Você pode implementar um método para comparar a estrutura dos Termos. Esse método compararia os subterfúgios também de maneira recursiva.

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

Em seguida, para verificar se há alguma estrutura muito específica, crie uma nova instância de termo com a estrutura desejada e passe para esse método, que ignora os valores e compara apenas os tipos de termos.

Isso funciona para o seu caso?

EDIT: editado acima do código para também ser capaz de buscar coisas das estruturas.

A consulta pode ser implementada assim:

//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);
}
    
por 25.08.2017 / 20:40
fonte