Relacionamento bidirecional entre classes

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.

17 Comentários

  1. T 28/03/2007 at 06:53 #

    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…

  2. Victor de Souza Couto 28/03/2007 at 08:09 #

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

    Até mais.

  3. Paulo Silveira 28/03/2007 at 10:38 #

    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.

  4. Ricardo Nakamura 28/03/2007 at 22:51 #

    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.

  5. T 29/03/2007 at 11:41 #

    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’!)…

  6. Paulo Silveira 29/03/2007 at 15:07 #

    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

  7. Fabio Kung 29/03/2007 at 15:13 #

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

  8. Tiago Silveira 29/03/2007 at 20:46 #

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

  9. Fabio Kung 30/03/2007 at 10:54 #

    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.

  10. Bruno Nicoletti 06/04/2007 at 17:07 #

    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

  11. Lucas Húngaro 13/04/2007 at 08:37 #

    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.

  12. Flávio Almeida 21/03/2011 at 08:35 #

    Esse tópico é antigo, mas atualmente tenho debatido sobre esse assunto. Acredito que um dos motivos mais atraentes (vejam, um dos motivos) para se implementar apenas o relacionamento unidirecional é o de bounded contexts provindo do Domain Driven Design. Com relacionamentos bidirecionais, fica muito mais difícil você defintir contextos de acesso bem definidos em um modelo RICO.

  13. marco 29/10/2011 at 21:57 #

    Relacionamento bidirecional é algo praticamente inevitável, principalmente em contabilidade em que se necessita de relatórios do tipo conta/centro de custo e o seu inverso centro de custo/conta. Não há um mais importante que outro. todos os sistemas financeiros terão o mesmo problema.

    Marco

  14. Dênis Schimidt 17/05/2012 at 09:09 #

    Achei interessante a abordagem do assunto. Porém seguindo nessa mesma linha OO e DDD será que faz sentido o motor conhecer o carro ou o carro seria a entidade raiz (agregada) e eu me comunicaria com o motor através do carro?

    Pensando eu como usuário, quando eu quero que um carro acelere eu não falo diretamente com o motor, mas sim com o carro e ele sim “fala” com o motor. Nesse caso o relacionamento poderia ser unidirecional.

  15. Ricardo Johannsen 17/08/2013 at 23:31 #

    tópico antigo mas a discussão ainda é muito válida, estes dias andei pensando bastante sobre o assunto, estou em uma dúvida braba em relação a esse tipo de relacionamento para construir uma view no JSF.Pegando o exemplo do Marco Conta/Centro de custo, no meu bean mesmo centro de custo sendo detalhe de conta eu trataria os dois de forma unidirecional ouseja no managed bean eu teria um objeto conta, um objeto centro de custo e um list de centro de custo da conta atualmente selecionada.pensei assim
    private Conta conta;
    private CentroCusto centroCusto;
    private List centrosCusto;
    e em algum método que selecionasse qual a conta atual,eu faria:
    centrosCusto = centroCustoService.findByConta(conta.getId());
    sendo assim, eu poderia, inserir,editar e remover centros de custos sem ter que salvar a entidade conta,faria a alteração só no centro de custo especifico e após um remove por exemplo eu só precisaria recarregar novamente a lista dos centros de custo com: centrosCusto = centroCustoService.findByConta(conta.getId());
    oque vocês acham dessa abordagem?

  16. Max Dolabella 24/05/2014 at 23:21 #

    Vê se isso ajuda. Adaptado de um exemplo de bidirecionalidade que meu professor me mostrou:

    Carro c = new Carro(new Motor(c));

Deixe uma resposta