Relacionamento bidirecional entre classes
Por Paulo Silveira em 28/03/07Hoje 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.