Composição no Corpo Humano
Vamos entender o relacionamento de composição entre classes através de uma analogia com o corpo humano.
O que é o corpo humano? É um sistema.
Do ponto de vista Conceitual, como um software, podemos interpretá-lo composto de Módulos, Domínios, Funcionalidades, Requisitos Funcionais, Requisitos Não Funcionais e Regras de Negócio.
Do ponto de vista Estrutural, também como um software, podemos interpretá-lo como um Namespace ou Pacotes, composto de Classes, classes compostas por outras classes, todas com seus métodos etc.
Uma Mão é composta por Dedos. Podemos entender a Mão como uma Classe, do Namespace Braço, e a classe Mão possui cinco composições da classe Dedo.
Uma mão comum é composta por cinco dedos: Polegar, Indicador, Médio, Anelar, Mindinho).
A representação em UML num diagrama de classes da composição entre Dedo e Mão segue abaixo.
Poderia citar ainda as composições da classe Dedo com a classe Osso, mas vamos parar por aqui para avançarmos no software. Quem tiver interesse em entender melhor a estrutura da mão humana, aqui está a fonte que consultei, conteúdo muito bom.
Para que serve a Composição
O relacionamento de Composição entre classes é muito útil na modelagem de software, tanto sob o ponto de vista Estrutural quanto Arquitetural.
Através do uso de Composição podemos fazer reuso de objetos, sem ter que duplicar as classes (instanciadas através dos objetos) na estrutura do sistema.
Considerando o paradigma da Orientação a Objetos, além do reuso, as composições nos permitem aproximar o projeto do software da vida real, como no exemplo da Mão e dos Dedos.
E acima de tudo, viabiliza um projeto bem organizado, o que é questão de sobrevivência para qualquer software.
Composição ou Herança?
A Herança deve ser utilizada quando há relação de ascendência ou decência. Mão não herda de Dedo, então não aplica-se herança. Boca não herda de Dente, também não se aplica. Mas Coração herda de Tecido, então aplica-se Herança. Tecido herda de Célula, aplica-se também a Herança.
Muitos profissionais utilizam pouco a Composição e abusam da Herança, o que não é saudável para a arquitetura de um sistema.
Eu penso que Composição gera menos acoplamento que Herança, de um modo geral. E também gera estruturas com maior coesão. Refatorar uma estrutura complexa amarrada em muita Herança é bem mais difícil que injetar dependência em classes dependentes em função de Composição.
Composição quando houver relação de composição, Herança quando houver relação de herança.
Ao fim deste post tem um vídeo nosso explicando melhor essa diferença.
Cuidado com o abuso
O relacionamento de composição entre classes deve ser usado com muito bom senso, pois abusar dele gera forte acoplamento em função das dependências geradas naturalmente entre as classes.
Uso inadequado e adequado da Composição
Vamos imaginar as seguintes classes: PC (PersonalComputer) Notebook, Tablet, Processador, Memória e Cooler.
Sabemos que PC, Notebook e Tablet possuem Processador, Memória e Cooler, e que estes últimos três são os mesmos para os três tipos de aparelhos.
Então, ao invés de criarmos cópias da estrutura de Processador, Memória e Cooler para cada aparelho que as utiliza, isolamos os três e os reaproveitamos nos respectivos aparelhos, favorecendo uma boa coesão e aproveitando do reuso.
Abaixo um exemplo das classes PC, Notebook e Tablet, sem o uso de Composição (ou outro recurso semelhante), com repetição das propriedades que são comuns aos três.
Como citado, os três aparelhos possuem Processador, Memória e Cooler, mas da forma que foi modelado no diagrama acima as propriedades das três classes foram replicadas em cada uma delas, impossibilitando reuso, acabando com a coesão, desaparecendo com separação de responsabilidades.
O melhor a fazer no cenário exposto é separar cada um dos três itens em classes distintas, e compor os dispositivos (PC, Notebook e Tablet) com os itens separados.
Abaixo segue nova versão do modelo ilustrando isso:
O novo modelo ficou organizado, fácil de evoluir, fácil de entender e com as responsabilidades bem separadas. Aplicamos o relacionamento de composição entre classes de maneira muito efetiva.
Abaixo segue um outro exemplo, num contexto diferente, porém com um modelo mais elaborado e detalhado, para termos algo mais próximo do dia a dia de um Analista de Sistemas.
/* Um detalhe importante: a direção do relacionamento deve ser do “compositor” para o “composto”. */
No modelo acima temos ClienteEntidade, que representa o Cliente, EnderecoEntidade, que representa um Endereço e ContatoEntidade, que representa um Contato de um cliente.
Vemos que existem três composições: duas composições de EnderecoEntidade para ClienteEntidade, representando assim as duas propriedades de Endereço do Cliente (EnderecoPrincipal e EnderecoSecundario) , e uma composição de ContatoEntidade para ClienteEntidade, representando a propriedade Contatos em ClienteEntidade.
Cliente possui apenas dois Endereços, logo, duas composições com multiplicidade de 1 para 1, devidamente identificadas no nome dos relacionamentos.
Essa identificação é fundamental para tornar o diagrama legível, pois sem elas fica confuso entender a razão de duas composições entre duas classes, favorecendo assim uma boa semântica.
Mas cliente pode possuir N (0 ou muitos) contatos, logo, aplicamos uma única composição com multiplicidade de 0 * 1 para 1 (0 ou muitos contatos para um cliente), utilizando para isso uma propriedade do tipo IList<> (uma lista de contatos).
Código Fonte da classe de Cliente
Assim fica o Código Fonte da classe ClienteEntidade.
Cada propriedade possui o tipo da classe que está a compondo.
E sintaticamente, como podemos ver no código, fica muito bem estruturado.
Abaixo, um vídeo nosso, explicando melhor a diferença entre os relacionamentos de Herança e de Composição.