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:
- Herança: Relacionamento caracterizado pela expressão "é um". Ex: Vendedor - Pessoa
- Associação Simples: É a forma mais fraca de relacionamento, onde as partes podem existir independentemente. Ex: Pessoa - Cachorro
- Agregação: Relacionamento mais forte que associação simples, onde a parte é necessária ao todo, mas não indispensável. Ex: Pessoa - Braco
- Composição: É o mais forte relacionamento entre classes, onde nem o todo e nem a parte podem existir separadamente. Ex: Pessoa - Coracao
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
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 :)
ResponderExcluirGostei tbm dos códigos em Scala, aparenta ser relativamente simples.
Muito bom o topico, parabéns.
[]'s
Obrigado Rafael. Obrigado também pela visita.
ResponderExcluirabraços
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!
ResponderExcluirContinua postando para nós! Bruno Reis