DEV Community

Laura Vivan Gonçalves
Laura Vivan Gonçalves

Posted on

Diagrama de Classes: Associação X Generalização X Agregação X Composição

Introdução

No mundo da Engenharia de Software podemos utilizar diferentes diagramas para modelar um sistema, desde seu comportamento até a sua estrutura.

No caso de sistemas que utilizam da Orientação a Objetos, o Diagrama de Classes se torna essencial para modelar os objetos presentes no sistema e suas relações.

Esse artigo traz um overview do Diagrama de Classes com base no livro UML Distilled: A Brief Guide to the Standard Object Modeling Language do autor Martin Fowler.

The Essentials

O autor divide os conceitos em 3 perspectivas: Conceitual, Especificação e Implementação.

  • Conceitual: da perspectiva conceitual o modelo não terá consideração pelo software a que ele possa ser aplicado, não considera os tipos de dados, a linguagem que será utilizada, etc. O modelo irá representar as coisas que ali existem e como elas são relacionadas entre si.
  • Especificação: se trata de olhar para as interfaces de um sistema e não como ele é implementado. O autor traz que, na verdade a chave da programação orientada a objetos é a interface da classe e não sua implementação, porém no mundo real isso é constantemente utilizado conjuntamente. Geralmente a palavra "tipo" será utilizada para falar sobre a interface de uma classe. Um tipo pode ter diferentes classes que o implementem, e uma classe pode implementar diferentes tipos.
    • Um exemplo simples: supondo que tenhamos uma máquina de suco e essa máquina possui um botão chamado "Fazer suco". A pessoa que está utilizando essa máquina, para ela não interessa como que a máquina faz o suco por dentro, para ela interessa que ao apertar o botão, será feito o suco. Aqui a interface é o botão, ou os possíveis botões que estarão disponíveis na máquina. O que tem dentro dela, é a implementação.
  • Implementação: o autor informa que a implementação é geralmente a perspectiva mais utilizada.

Apesar de a perspectiva não ser uma parte formal da UML é extremamente útil na hora de modelar. E a UML pode ser utilizada com qualquer uma das perspectivas vistas acima.

Associação

A associação é a forma mais comumente utilizada na hora de representar relações entre objetos. Essa relação se torna permanente durante todo o ciclo de vida dos objetos.

Associações podem ser representadas por uma linha contínua ligando 2 objetos, elas podem ou não ter um nome, e elas podem ser uni ou bi direcionais. Essa direção é o que indica a navegabilidade entre os objetos.

Vamos tomar como exemplo a Figura 1, que mostra a relação de um pedido-cliente.

Figura 1 - Pedido x Cliente
Diagrama de Classes: exemplo pedido x cliente


Fonte: elaborado pela autora (2025)

Label: role name

Nesse exemplo não está sendo utilizado um role name, porém, ele poderia existir para identificar o papel da classe na associação. Nesse caso, utiliza-se o próprio nome da classe: Pedido e Cliente.

Esse role name é indicado nas extremidades da linha, e pode servir como uma informação extra para auxiliar no entendimento.

Multiplicidade

A multiplicidade é representada por 0..., 1 ou 1...1, 1..., 0...1, etc. Ela é utilizada para indicar a quantidade de objetos que podem participar da relação. O asterisco indica vários (em teoria infinitos).

Na hora de construir o diagrama, é importante que a multiplicidade vai de onde sai para onde chega. No caso de pedido, ele pode estar relacionado a somente 1 cliente, portanto o referencial da multiplicidade fica na ponta da classe Cliente. Já o cliente pode estar relacionado a 0 ou mais pedidos.

Perspectivas

Na visão das perspectivas, a especificação não deve indicar se a relação entre eles é um ponteiro, uma referência, ou se vai ser buscado em um banco de dados. Já a implementação sim.

Especificação:

class Pedido {
  public Cliente getCliente();
  public Set getItemsDoPedido();
Enter fullscreen mode Exit fullscreen mode

Implementação:

class Pedido {
  private Cliente _cliente;
  private Set _itemsDoPedido;

class Cliente {
  private Set _pedidos;
Enter fullscreen mode Exit fullscreen mode

Note que na especificação indica que por meio do pedido será possível buscar um cliente e também os itens daquele pedido, porém, não mostra como isso será realizado. Já a implementação mostra que para isso será utilizado referência. Muitas linguagens de programação trabalham com referências em suas classes como é o caso de Java, JavaScript/TypeScript, C# e Python, etc.

Navegabilidade

A associação sem a navegabilidade é representada somente por uma linha, porém, ela pode utilizar de setas para indicar navegação (Figura 1).

Nesse caso ela mostra que a partir de um pedido é possível saber quem é o cliente. Se a seta fosse o contrário, seria possível descobrir o pedido a partir do cliente. Sem a seta, dependendo do contexto, indica que é uma relação que ainda não se sabe a navegabilidade ou que ambos conseguem se "ver" mutualmente.

Mesmo sem o uso da seta, tanto na especificação como na implementação é possível ter uma ideia dessa navegabilidade como foi abordado no tópico perspectivas.

Generalização

A Generalização envolve o conceito de supertypes (supertipos) e subtypes (subtipos), em que, os subtipos possuem diferenças e similaridades entre si, e suas similaridades podem ser "reunidas" no supertipo.

Conceitualmente, o subtipo será um subtipo do supertipo se todas as instâncias do subtipo forem por definição instâncias do supertipo. Portanto, o subtipo é um tipo especial do supertipo. Tudo o que for verdade para o supertipo (associações, atributos, métodos) é também verdade para o subtipo.

Isso na perspectiva da especificação é entendido como, a interface de um (subtipo) precisa incluir todos os elementos da interface do outro (supertipo). Isso é o que chamamos de Princípio da Substituibilidade. Mesmo que o subtipo possa ter coisas que funcionam de forma diferente do supertipo, para o supertipo é indiferente, deve funcionar corretamente independente do subtipo sendo utilizado (polimorfismo).

Portanto, deve-se conseguir utilizar um subtipo onde é solicitado o supertipo, sem precisar adaptar algo.

Figura 2 - Supertipo e subtipos
Diagrama de Classes: exemplo generalização


Fonte: elaborado pela autora (2025)

Na implementação, isso é conhecido como Herança. Uma subclasse herda todos os atributos e métodos da superclasse podendo sobrescrever métodos se necessário. Portanto, subclasses são uma forma de implementar subtipos.

Outra forma é por meio de delegation (delegação) que ao invés de herdar, cria-se um atributo interno que representa a superclasse e delega a ela coisas a serem feitas. Isso é bom quando se precisa evitar acoplamento forte e rigidez de classes.

Advanced Concepts

Da associação deriva-se a agregação, e a composição se mostra um tipo mais avançado de agregação (agregação forte).

A Figura 3 será o exemplo para melhor compreensão dos conceitos.

Figura 3 - Agregação e Composição
Diagrama de Classes: exemplo agregação e composição


Fonte: elaborado pela autora (2025)

Nos tópicos abaixo são abordadas cada uma.

Agregação

No processo de modelagem do meu sistema (Acessiweb) para o meu Trabalho de Conclusão de Curso, em diversos momentos me vi confundindo conceito (design, regras de domínio) e implementação (código). E isso se fez bastante presente na parte de agregação x composição.

Um dos conceitos core nesse cenário é a exclusão em cascata (se um objeto morre, os demais objetos relacionados morrem com ele). Na prática, qualquer associação com forte dependência (multiplicidade 1..1 ou 1..*) estará sujeita a sofrer essa ação por questões lógicas ou de integridade e isso precisará ser definido ou pela regra de negócio da aplicação, ou pelo banco de dados, ou ambos.

Se um pedido está relacionado a um cliente, a partir do momento que esse cliente deixar de existir, todos os seus pedidos, incluindo os itens dos pedidos também deixarão de existir. Isso é algo "esperado", "lógico", porém precisa ser implementado. E aí entra a diferença de agregação e composição, no quesito do que é esperado de seu comportamento ao ser implementado.

A agregação é um tipo de associação que nos indica uma fraca relação entre dois objetos em que um deles será a parte (objeto 1) de um todo (objeto 2). A parte (objeto 1) vive independente do todo (objeto 2).

Supondo que há um objeto que define o estilo de uma forma geométrica (outro objeto). Ao instanciar a classe de estilo (criar o objeto propriamente), esse estilo pode ser utilizado em diferentes formas geométricas. Se uma forma geométrica for excluída, o estilo em tese não precisa ser excluído. Portanto, o estilo faz parte da forma geométrica, mas não depende dela para existir.

Um exemplo extraído do Acessiweb é a relação entre Projeto e Usuário.
Um usuário pode criar zero ou vários projetos, e um projeto sempre pertence a um único usuário.

Minha principal dúvida ao modelar essa relação foi entender até que ponto o projeto dependia da existência do usuário. Em termos de regra de negócio, faz sentido que, ao excluir o usuário, seus projetos também sejam excluídos, pois são de sua responsabilidade e autoria. No entanto, do ponto de vista estrutural (de modelagem), o projeto é uma entidade independente — possui atributos próprios e poderia existir isoladamente, com a associação ao usuário sendo apenas um de seus vínculos. Então dessa forma, é uma decisão excluir ou não o projeto, mas estruturalmente (conceito), ele não precisa deixar de existir se o usuário não existir mais.

A regra de negócio do sistema exigia que um projeto só pode ser criado por um usuário, então necessitaria haver a instância daquele usuário e ela precisaria estar relacionada ao projeto. Além disso, uma vez vinculado a um usuário, essa associação é imutável, ou seja, o projeto não troca de usuário. Mesmo assim, essa relação não deixa de ser uma agregação.

Pelo exemplo da Figura 3, um livro pode estar relacionado com 1 ou mais autores, e um autor pode estar relacionado com 0 ou mais livros. Se um livro for excluído do sistema, o autor não será excluído junto com ele, pois ele pode ter mais livros relacionados. Da mesma forma que se um autor for excluído, o livro está relacionado com outros autores, e é uma entidade própria.

Composição

A composição é uma variação mais forte da agregação. O objeto-parte que pertencer ao objeto-todo deve pertencer somente a um único objeto-todo, em que se espera que as partes morram com o todo.

Supondo que haja um objeto Ponto e outro objeto Aresta e outro objeto Círculo. Os objetos Ponto e Aresta são objetos-parte do objeto-todo Círculo. Isso porque, um ponto só pode estar presente em uma forma geométrica por vez (conceitualmente), ou seja, ao instanciar a classe Ponto e criar um objeto de ponto não é possível utilizar esse objeto instanciado com outras formas geométricas. No momento em que o círculo é removido, todos os seus pontos e arestas devem ser removidos junto com ele, visto que não fazem sentido existirem sem ele.

Conceitualmente a composição indica que esse objeto-parte não faz sentido sem o seu objeto-todo. Na prática, é permitido criar uma instância de ponto e ela não é automaticamente removida do sistema, da mesma forma que na agregação, porém, é lógico que ela tenha que ser excluída. Cheguei a me perguntar se esse ponto não poderia ser instanciado fora e por sua vez, sendo instanciado somente no momento que o objeto-todo fosse instanciado, ao ser excluído do sistema, as instâncias de suas partes também seriam excluídas.

Então mesmo que na implementação seja permitido utilizar um Ponto, por exemplo, em diferentes instâncias de círculo, em termos de design isso é incorreto, pois trata-se de composição.

Exemplo de implementação incorreta de um caso de composição:

const ponto = new Ponto(1, 2);

const circuloA = new Circulo(ponto);

delete circuloA;
const circuloB = new Circulo(ponto); 
Enter fullscreen mode Exit fullscreen mode

Mesmo que não seja necessário, é possível forçar a implementação da composição da forma que eu havia pensado em um momento (como falei acima):

class Circulo {
  constructor(x, y, r) {
    this.centro = new Ponto(x, y);
  }
}
Enter fullscreen mode Exit fullscreen mode

Ao instanciar ponto dentro de círculo.


Agregação e Composição, representação

Na hora de construir o diagrama e indicar as relações de agregação e composição, sempre lembre da navegabilidade de onde está saindo para onde está indo.

No caso da agregação, o losango branco é conectado ao objeto-todo. Da mesma forma que o na composição, o losango preto é conectado ao objeto-todo. A diferença é que na composição o objeto-todo é dono do objeto-parte, e na agregação, o objeto-todo utiliza o objeto-parte mas não é dono exclusivo dele.

Portanto, "o losango fica do lado de quem controla a relação".

Referências

FOWLER, Martin. UML distilled: a brief guide to the standard Object Modeling Language. 2nd ed. Reading, MA: Addison-Wesley, 2000

Top comments (0)