quarta-feira, 29 de julho de 2009

Princípios de projeto - Parte IX - Relacionamento entre classes

Aproveitando o meu post nesta thread do JavaFree.org, vou desenvolver aqui um pouco mais o relacionamento entre classes.

As decisões quanto a esse aspecto, podem levar a sua aplicação a ter um bom ou um mau design. Alguns conceitos, como os que abordam a questão da coesão, são decisivos para uma boa escolha para o relacionamento entre classes.


Existem quatro tipos principais de relacionamento, a saber:
  1. Herança: Relacionamento caracterizado pela expressão "é um". Ex: Vendedor - Pessoa
  2. Associação Simples: É a forma mais fraca de relacionamento, onde as partes podem existir independentemente. Ex: Pessoa - Cachorro
  3. Agregação: Relacionamento mais forte que associação simples, onde a parte é necessária ao todo, mas não indispensável. Ex: Pessoa - Braco
  4. Composição: É o mais forte relacionamento entre classes, onde nem o todo e nem a parte podem existir separadamente. Ex: Pessoa - Coracao
Alguém pode dizer, que no exemplo da associação, Pessoa pode conter, por exemplo, um List<Cachorro>, mas teríamos um bom problema de design, já que a classe Pessoa estaria sofrendo de mixed-role cohesion. Em linhas gerais:
Uma classe contém um elemento que faz parte do domínio, mas não faz parte da abstração dessa classe.
O exemplo do Cachorro e Pessoa, inclusive, é o exemplo de Page-Jones no seu livro.

Isto é, para o domínio da aplicação atual, uma Pessoa terá sempre um Cachorro, mas, se em uma nova aplicação, não houverem Cachorros e você quiser (e deve) reusar a classe Pessoa? A solução para esse problema de design é a criação de uma classe intermediária que fará o relacionamento entre Pessoa e Cachorro.

Até aqui, nenhuma novidade com relação a thread do JavaFree.

Entretanto, depois que formulei a resposta, uma pulga ficou atrás da minha orelha. Os exemplos de agregação e composição, funciona em termos acadêmicos, pois, no mundo real propriamente dito, uma pessoa não vive sem um coração, mas, normalmente, quando estamos modelando um sistema de vendas, por exemplo, para o domínio do cliente, esta composição é falsa, já que não precisaríamos modelar um coração e, claramente, quando fossemos reusar a classe pessoa, notaríamos um grave problema de mixed-role cohesion. Certamente, seria difícil reutilizar a classe Pessoa, em uma aplicação que não fosse, por exemplo, de doação de órgãos.

Este post marcará também a minha primeira exemplificação baseada em Scala. Então, vamos lá.

Usarei o exemplo da composição, que necessariamente implicaria em atributo.

Primeiro a classe Pessoa:
class Pessoa {
  private var age = 0
  
  def addAge(amount: Int) { age += amount }
}
Agora a classe Coracao:
class Coracao {
  private var beatMinute = 0
  
  def accelerate(amount: Int) { beatMinute += amount }
  def decelerate(amount: Int) { beatMinute -= amount }
  def getBeat(): Int = { beatMinute }
}
Na explicação sobre composição, o que eu disse é que, obrigatoriamente, teríamos algo assim:
class Pessoa {
  private var age = 0
  private var coracao = new Coracao
  
  def addAge(amount: Int) { age += amount }
}
Entretanto, como eu disse anteriormente, esta modelagem possui uma "doença" de coesão: mixed-role cohesion, o que impossibilitaria a reusabilidade sem alterações no código. Neste caso, a alteração seria remover a dependência de Coracao com Pessoa.

A solução, neste caso, seria criar uma classe intermediária, Corpo que teria a dependência com Coracao. Algo como:
class Corpo (coracao: Coracao)
Pronto, já alocamos a classe Coracao, e estamos livres de problemas de coesão. Mas, agora, falta modelar o relacionamento Pessoa - Corpo. Inserir um atributo Corpo em Pessoa, pois, no mundo real Pessoa tem Corpo, resultaria no mesmo problema de coesão, pois a classe Pessoa só serviria em uma aplicação médica, por exemplo.

Neste caso, a OO nos permite fugir um pouco do mundo real, pois sua definição diz "que o desenvolvimento OO aproxima a aplicação do mundo real" e não que reflete exatamente. A solução, sob meu ponto de vista, é simplesmente colocar um atributo Pessoa em Corpo, já que Corpo está representando o relacionamento. Dessa forma, nossa modelagem resultaria nisso:
class Pessoa {
  private var age = 0
  
  def addAge(amount: Int) { age += amount }
}
class Corpo (pessoa: Pessoa, coracao: Coracao)
class Coracao {
  private var beatMinute = 0
  
  def accelerate(amount: Int) { beatMinute += amount }
  def decelerate(amount: Int) { beatMinute -= amount }
  def getBeat(): Int = { beatMinute }
}
Existem vantagens e desvantagens nessa abordagem. Uma desvantagem é o aumento da granularidade da aplicação, significando que existem mais classes para dar manutenção, não significando, necessariamente, em aumento da dificuldade de manutenção. E as vantagens são: responsabilidades das classes bem definidadas, facilitando a manutenção; e baixo acoplamento, facilitando o reuso. Na minha opinião, a desvantagem é muito pequena se comparada com as vantagens.

Mas no novo modelo, onde a composição? Simples, entre Corpo e Coracao. Não estamos contando nenhuma mentira para o mundo real, já que não é possível existir um Corpo sem Coracao, pelo menos vivo.

Um ótimo exemplo de composição, muito mais próximo ao mundo do densenvolvimento de sistemas, é o relacionamento entre Pedido e ItemPedido. Não é possível conceber um sistema de pedidos em que estes não sejam compostos por itens. E é impossível imaginar um ítem de pedido que não tenha pedido. Neste caso, as classes Pedido e ItemPedido estariam em um pacote e seria fácil a reutilização em qualquer sistema que necessite de pedidos.

Parte I - Introdução
Parte II - Correção
Parte III - Design por contrato
Parte IV - Flexibilidade
Parte V - A Lei de Demeter
Parte VI - Eficiência
Parte VII - Coesão
Parte VIII - Usabilidade

3 comentários:

  1. Realmente muito interessante a abordagem, na verdade nunca havia pensando em reutilizar classes simples (Representam entidades do banco), e achei realmente bacana a ideia, pode-se evitar algum copy/paste com isso :)

    Gostei tbm dos códigos em Scala, aparenta ser relativamente simples.
    Muito bom o topico, parabéns.

    []'s

    ResponderExcluir
  2. Obrigado Rafael. Obrigado também pela visita.

    abraços

    ResponderExcluir
  3. Celso, excelente o artigo! Nos livros do mercado mostram como implementar agregação, composição. Mas não mostra como implementar uma associção simples. Ótimo! Sobre a separação das classes por responsabilidade, sobre a quantidade exagerada de classes, acho essa separação muito mais benéfica, mesmo que o objeto seja um pouco anêmico um pouco! Parabéns!
    Continua postando para nós! Bruno Reis

    ResponderExcluir