Relacionamento bidirecional entre classes

Por Paulo Silveira em 28/03/07

Hoje estava pareando com o Lucas Cavalcanti, em um sistema de alunos para a Universidade de São Paulo. Lucas é o novo estagiário da Caelum, e o primeiro da parceria USP-Caelum, onde colocamos um aluno para trabalhar nos projetos da universidade, e ele é orientado, treinado e coordenado pela empresa.

Começamos um projeto novo, e nas primeiras classes de modelo do Hibernate tinhamos uma relação OneToOne. Muitas pessoas ficam em dúvida se devem fazer o relacionamento bidrecional, e isso é um ponto importante a ser considerado não apenas no uso do Hibernate, e sim em qualquer situação.

Para exemplificar, imagine a classe Carro e a classe Motor, sendo que um Carro tem um e apenas um Motor, e o Motor pertence a um único Carro.

Como modelar essas classes? Devemos colocar os atributos correspondentes nas duas classes (relacionamento bidirecional)? Apenas Carro tem de saber qual é o seu Motor? Ou é o Motor quem deve saber sobre o seu Carro? Vamos ver o código no caso de optarmos pelo relacionamento bidirecional:

Motor m = new Motor();
Carro c = new Carro();
c.setMotor(m);
m.setCarro(c);  // hummm….

Repare que se não tivessemos m.setCarro(c) cairíamos em um estado inconsistente para o nosso modelo. Poderia ser ainda pior: imagine alterar o Motor m do nosso Carro c criados anteriormente:

Motor outroMotor = new Motor();
c.setMotor(outroMotor);
// e o Motor m? continua apontando para o Carro c

Agora o antigo Motor m ficou referenciando a um Carro que não o contem, e o outroMotor aponta para Carro algum (null)! Claro que temos como tentar nos precaver dessa situação, mas sempre teremos um código mais complicado e muito sucetível a deixar o estado de seus objetos incosistente.

Imagine agora se essas classes são entidades do Hibernate. Se elas não estiverem concordando a respeito do relacionamento, que lado vai ser considerado na hora de persistir os dados? Quem manda, o Carro ou o Motor? Ou deve ser lançada uma exceção? É para isso que temos os relacionamentos marcados como inverse (os que são mappedBy nas anotações): esse lado do relacionamento não é considerado para a atualização.

Isso tudo pode ser muito mais complicado em relacionamentos 1:N e N:M. O conselho é tentar evitar o relacionamento bidirecional, e nunca cria-lo sem uma real necessidade, assim como já comentamos sobre evitar herança e evitar getters e setters.

Algumas pessoas sugerem que as novas linguagens OO deviam suportar esse tipo de auto ajuste da outra ponta do relacionamento, usando sintaxes novas. O quão dependente suas classes são uma das outras, e quanto mais ciclos elas formam, geram um número que é usado como métrica para avaliar a qualidade de um projeto orientado a objetos. O plugin do Eclipse ByeCycle mostra o grafo de relacionamento entre suas classes. Se houver um ciclo, é um sinal de perigo.

No fim da noite terminamos algumas entidades do hibernate e algumas queries do DAO, e os respectivos testes unitários através de um banco H2 criado e derrubado em memória para cada teste. Foi extremamente produtivo e interessante.

11 Comments »

  1. Neste caso, poderia ser feito o seguinte:
    - o mapeamento feito diretamente nos atributos (podem ser privados, inclusive), o que ajuda a manter o encapsulamento, não expondo tudo com setters se não for conveniente
    - Motor receberia Carro como parâmetro do construtor, atribuindo-o a um atributo final e chamando carro.setMotor(this)
    - o método carro.setMotor(Motor) se asseguraria que ‘motor.getCarro() == this’, lançando uma IllegalStateException caso contrário.
    - ambas classes teriam o getter da outra

    E relacionamentos bidirecionais são muito úteis, nos poupam um monte de HQL…

    Comment by T — March 28, 2007 @ 6:53 am

  2. Huuuuuuuum.
    Ainda estou iniciando, mas deu para entender o problema.
    É fácil fácil se perder no sistema dessa forma.

    Até mais.

    Comment by Victor de Souza Couto — March 28, 2007 @ 8:09 am

  3. Oi T (!). Sua modelagem també não está correta. Continua com as mesmas brehas que o unidirecional teria, ou o bidirecional ingênuo.

    Poderíamos ter DOIS motores para um mesmo Carro, bastando passar o carro do motor1 para o construtor do motor2. E aí o carro ficaria apontando apenas para o último motor: estado totalmente incosistente.

    Pra consertar isso daria um trabalhão, para ver se o Carro já não tem um Motor, e aí decidir se um Carro pode mudar de Motor, etc. Sem contar que você ficar com um setter que só deve ser usado pelo construtor da outra classe: simplesmente bizarro. Em outras palavras, está com um abacaxi na mão, mas como eu disse no post, da sim pra se precaver, como você mesmo tentou. Repare que se o relacionamento fosse N:M você estaria numa situação ainda pior, e o unidirecional tem muitas vantagens.

    Comment by Paulo Silveira — March 28, 2007 @ 10:38 am

  4. Olá Paulo.
    Acompanho sempre seus artigos e o blog da Caelum(gostei muito do evitar herança), mas nunca deixei um comentário ou uma sugestão. Esse artigo me interessou muito, porque já tive e ainda tenho esse problema, e ainda não sei como ter uma boa definição de quando usar ou não o relacionamento bidirecional. Mas eu ainda não entendi quando devo utilizar esse recurso.
    Devo utilizar sempre que for útil a minha aplicação e poder sofrer os efeitos mencionados ? Ou simplesmente evitar ao máximo esse recurso, usando somente quando não tiver outro jeito(tipo quando o prazo é pra ontem) ?
    Um abraço.

    Comment by Ricardo Nakamura — March 28, 2007 @ 10:51 pm

  5. Hum… ok, viajei. Afe, acho que estou convivendo demais com esses objetos burros que usam aqui no trabalho (e que querem assim, pq é ‘mais simples’!)…

    Comment by T — March 29, 2007 @ 11:41 am

  6. Ola Ricardo

    Creio que eu tenha sido muito xiita no artigo, tanto que recebi algumas críticas construtivas do próprio pessoal da Caelum (como a do T, logo acima).

    Creio que a regra seja a mesma dos getters e setters: pense se você realmente precisa acessar a relação através do outro lado. Se não tiver uma razão forte, não crie. Vale lembrar que em OO não existem regras mágicas e fixas….

    abraços

    Comment by Paulo Silveira — March 29, 2007 @ 3:07 pm

  7. O comentário do T realmente não soluciona completamente os problemas, mas os ameniza e é uma alternativa perfeitamente aceitável em muitos contextos.

    Comment by Fabio Kung — March 29, 2007 @ 3:13 pm

  8. Opa! Mas aí perde todo sentido usar POJOs!

    Relacionamento bidirecional é uma maravilha. Além de ser super prático (pois vc pode dar load() num objeto só e facilmente acessar o outro), também abre várias portas, por exemplo na montagem de queries e na especificação de operações em cascata (@Cascade).

    Existe um e apenas um jeito de garantir que um relacionamento entre dois objetos Java (quer eles sejam persistentes ou não) está sempre em ordem: ambos devem trabalhar em conjunto para garantir a consistência!

    Ou seja, o setter do Carro que recebe o Motor “m” deve verificar se o novo valor é diferente do antigo e, em caso afirmativo, notificar ambos os motores. O motor antigo deve ter sua propriedade “carro” alterada pra null, e o novo, para “this”.

    Ao mesmo tempo, o Motor tem que ter um código parecido. Esse código é delicado, e se mal construído pode gerar um loop infinito (que em Java acaba provocando uma StackOverflowException).

    Quando os relacionamentos envolvem coleções, eu costumo criar métodos add() e remove() no objeto que contém a coleção. De preferência, esses métodos têm visibilidade reduzida. Quando não é possível, eles passam a fazer parte da API pública e a coleção fica então protegida. Quando tudo mais falha, eu apelo pra o padrão Decorator: crio um componente @Embedded que manipule a coleção do jeito certo e implemente a interface adequada (e.g. java.util.List).

    Comment by Tiago Silveira — March 29, 2007 @ 8:46 pm

  9. Concordo plenamente com você Tiago, e o Paulo provavelmente também.

    Mas como você mesmo mostrou, manter a consistência dos relacionamentos bidirecionais não é tarefa fácil. Por isso o Paulo chamou a atenção para o uso indiscriminado desse tipo de relacionamento.

    Usar não é proibido, até acho necessário em muitos casos. Mas é preciso usar com cautela :D.

    Comment by Fabio Kung — March 30, 2007 @ 10:54 am

  10. Legal, mas tenho uma duvida que permanece.
    Digamos que o modelo mudasse para este caso:

    - Carro possui um motor
    - Moto possui um motor
    - Maquina de Lavar Roupas possui um motor
    - Motor ???

    Ok.. Carro e motor poderiam ser uma extensao de veiculo, neste caso o Motor possuiria um veiculo… mas e neste caso que temos uma maquina de lavar roupas no meio! (Ok, talvez o motor naum seja igual e neste caso teriamos um novo tipo de motor… mas vamos ignorar isso por enquanto)

    Qual seria a melhor saida pensando em OO?

    Um relacionamento bidirecional com o uso de uma interface representando todos que possuem motor??
    Um relacionamento unidirecional?
    ou outro qualquer…

    Pessoalmente eu evito o relacionamento bidirecional nestes casos para evitar tambem a criacao de uma interface como essa, mas a duvida continua me perseguindo… ainda mais depois deste comentario do Tiago que nos lembra sobre os beneficios do Hibernate no aspecto de navegacao nos objetos…

    []’s

    Comment by Bruno Nicoletti — April 6, 2007 @ 5:07 pm

  11. Cheguei um pouco atrasado, mas lá vai:

    Tiago, achei sua abordagem muito válida. Você conseguiu explicar muito bem o “código em prosa”, mas poderia colocar um exemplo simples em código? Creio que isso elimina qualquer possível ambiguidade para quem quiser usar a mesma abordagem.

    Comment by Lucas Húngaro — April 13, 2007 @ 8:37 am

RSS feed for comments on this post. TrackBack URL

Leave a comment