A maioria dos tipos de diagrama UML vive nos livros didáticos. O diagrama de classes é o que os engenheiros ainda desenham: uma imagem das coisas de que um sistema trata, do que cada uma guarda, do que ela sabe fazer e de como elas se relacionam. Bem desenhado, ele encerra discussões sobre o modelo de domínio antes que alguém escreva uma migração ou uma API.
Este artigo constrói um diagrama de classes UML para uma pequena plataforma de cursos online, uma decisão por vez, e explica a notação à medida que ela aparece: visibilidade, multiplicidade, associação, composição, herança e interfaces. No fim, mostra os erros que tornam diagramas de classes inúteis e dá uma olhada rápida em como o diagrama vira código.
Quando o diagrama de classes é a imagem certa
A especificação UML 2.5.1 divide seus diagramas em diagramas de estrutura e diagramas de comportamento. O diagrama de classes é um diagrama de estrutura: mostra tipos, seus atributos e operações e os relacionamentos entre eles, sem nenhuma noção de tempo.
| Pergunta que você precisa responder | Diagrama |
|---|---|
| Quais conceitos existem, o que sabem, como se relacionam | Diagrama de classes |
| Como os dados são armazenados: tabelas, chaves, chaves estrangeiras | Diagrama ER |
| O que acontece, e em que ordem, entre participantes | Diagrama de sequência |
| Por quais estados um objeto passa | Diagrama de máquina de estados |
Um diagrama de classes vale a pena para um modelo de domínio, para os tipos públicos de uma biblioteca ou para um design que você está prestes a construir. Não vale a pena como despejo de todas as classes de um repositório: uma IDE mostra isso melhor, e a imagem fica desatualizada no próximo commit.
A notação em duas tabelas
Uma classe é uma caixa com até três compartimentos: o nome, os atributos e as operações. Qualquer compartimento pode ser omitido.
| Elemento | Notação | Significado |
|---|---|---|
| Classe abstrata | Nome em itálico, ou {abstract} |
Não pode ser instanciada; as subclasses a completam |
| Interface | «interface» acima do nome |
Um contrato de operações |
| Visibilidade | + public, - private, # protected, ~ package |
Quem pode usar o recurso |
| Atributo | name: Type [multiplicity] = default |
Os dados que o objeto guarda |
| Operação | name(param: Type): ReturnType |
O comportamento que o objeto oferece |
| Recurso estático | Sublinhado | Pertence à classe, não a uma instância |
Relacionamentos são linhas entre classes, e as pontas carregam a maior parte do significado:
| Relacionamento | Linha | Leia como |
|---|---|---|
| Associação | Linha contínua; uma ponta de seta aberta marca uma ponta navegável | "está relacionado a" |
| Agregação | Linha contínua, losango vazio no todo | "agrupa"; as partes vivem por conta própria |
| Composição | Linha contínua, losango preenchido no todo | "é feito de"; as partes vão embora com o todo |
| Generalização | Linha contínua, triângulo vazio no pai | "é um tipo de" |
| Realização | Linha tracejada, triângulo vazio na interface | "implementa" |
| Dependência | Linha tracejada, ponta de seta aberta no destino | "usa"; uma mudança no destino pode quebrar a origem |
A composição é a rígida. Pela especificação, um objeto parte pertence a no máximo um composto por vez e, quando o composto é excluído, suas partes são excluídas junto. A agregação compartilhada, o losango vazio, é a vaga: a própria especificação diz que sua semântica precisa varia conforme a área de aplicação e o modelador. Use-a só se a sua equipe concordar sobre o que ela significa; uma associação simples diz o mesmo com menos debate.
Multiplicidade
A multiplicidade diz quantos objetos podem estar em uma ponta de uma associação: 1 para exatamente um, 0..1 para opcional, * (o mesmo que 0..*) para qualquer quantidade, 1..* para pelo menos um e intervalos como 2..5. Escreva-a na ponta, ao lado da classe que ela conta. Leia Course 1 — 1..* Module como "um curso tem um ou mais módulos, e cada módulo pertence a exatamente um curso".
Multiplicidades ausentes são o motivo mais comum para um diagrama de classes começar discussões em vez de encerrá-las. No exemplo abaixo, todas as associações têm as duas pontas preenchidas.
O exemplo: uma plataforma de cursos online
Comece por frases simples, não por caixas:
- Instrutores publicam cursos. Um curso tem um ou mais instrutores.
- Um curso é feito de módulos ordenados, e um módulo de aulas ordenadas.
- Uma aula é um vídeo, um quiz ou uma tarefa.
- Quizzes e tarefas recebem nota; vídeos não.
- Alunos se matriculam em cursos. Uma matrícula acompanha o progresso e guarda as entregas do aluno.
- Um aluno que conclui um curso recebe um certificado por aquela matrícula.
- Os cursos são agrupados em categorias, e um curso pode estar em várias.
Usuários: uma classe, dois papéis
O primeiro impulso é fazer de Student e Instructor subclasses de User. Isso quebra na primeira vez que um instrutor faz o curso de outra pessoa: na maioria das linguagens de programação, um objeto tem uma única classe a vida toda, então não pode ser as duas coisas nem mudar depois. Modele os papéis como associações: um User ministra cursos, e um User tem matrículas. Reserve a herança para o que é um tipo de algo a vida toda, como um quiz ser um tipo de aula.
Composição quando os ciclos de vida estão ligados
Um módulo não significa nada fora do seu curso, nem uma aula fora do seu módulo. Excluir um curso exclui os dois. Isso é composição: um losango preenchido em Course e em Module, com 1 no todo. Categorias são diferentes. Excluir a categoria "Engenharia de dados" não pode excluir seus cursos, então de Category para Course há uma agregação com * nas duas pontas.
Herança mais uma interface
Lesson é abstrata; VideoLesson, Quiz e Assignment herdam seu título e sua posição. A avaliação atravessa essa árvore: quizzes e tarefas recebem nota, vídeos não, e uma revisão por pares adicionada no ano que vem poderia receber nota sem ser uma aula. Isso é uma interface, Gradable, realizada por Quiz e Assignment. GradingService depende de Gradable, não das classes concretas, e a seta de dependência tracejada diz exatamente isso.
Uma associação com dados próprios
Enrollment fica entre User e Course. A UML tem uma notação própria para isso, a classe de associação, ligada à linha por uma linha tracejada. A maioria das equipes a desenha como uma classe comum com duas associações, porque ela tem identidade, dados e comportamento próprios: a data da matrícula, o progresso, as entregas e no máximo um certificado.
Conteúdo do diagrama
- Category
- Course
- User
- Module
- Enrollment
- Certificate
- Lesson
- Submission
- VideoLesson
- Quiz
- Assignment
- Gradable
- GradingService
- groups
- teaches
- enrolls
- for
- earns
- holds
- answers
- «use»
Como ler:
- No alto à esquerda, o catálogo.
Courseé composto deModule, que é composto deLesson, com losangos preenchidos no todo.Categoryagrupa cursos com um losango vazio. - Embaixo à esquerda, os tipos de aula.
Lessoné abstrata.VideoLesson,QuizeAssignmentapontam para ela com triângulos vazios. - À direita, o aprendizado. Um
Userministra cursos, com1..*instrutores por curso, e tem qualquer número de matrículas, cada uma para exatamente um curso. UmEnrollmentguarda entregas e rende no máximo umCertificate. - Embaixo à direita, a avaliação.
QuizeAssignmentrealizamGradablecom linhas tracejadas e triângulos vazios. CadaSubmissionresponde a um item avaliável, eGradingServicedepende apenas da interface.
Erros comuns
- Todas as classes do código. De dez a quinze classes por diagrama é um bom limite. Divida um modelo maior por área ou mostre o resto como nomes sem compartimentos.
- Getters e setters como operações. Liste operações com significado de domínio, como
publish()ouenroll(), e não os métodos de acesso. - Tabelas desenhadas como classes. Atributos como
courseIdpertencem ao diagrama ER. Num diagrama de classes, a linha de associação é a referência. - Herança para papéis. Se um objeto pode ganhar ou perder o papel, é uma associação, não uma subclasse.
- Losangos sem significado. Se ninguém sabe dizer o que excluir o todo faz com as partes, use uma associação simples.
- Detalhes de implementação no nível de domínio. Deixe de fora anotações de framework, DTOs e repositórios, a menos que o diagrama seja sobre eles.
Do diagrama ao código
O diagrama vira código diretamente. Em TypeScript:
interface Gradable {
maxScore(): number;
autoGrade(submission: Submission): Score | null;
}
abstract class Lesson {
constructor(public title: string, public position: number) {}
}
class Quiz extends Lesson implements Gradable {
questions: Question[] = [];
maxScore(): number {
return this.questions.length;
}
autoGrade(submission: Submission): Score | null {
return submission.scoreAgainst(this.questions);
}
}
class Course {
private readonly modules: Module[] = []; // composition: owned and removed with the course
categories: Category[] = []; // aggregation: categories exist on their own
addModule(title: string): Module {
const created = new Module(title, this.modules.length);
this.modules.push(created);
return created;
}
}
A composição vira posse: o curso cria seus módulos, e nada mantém um módulo vivo depois que o curso deixa de existir. A agregação vira uma referência a um objeto com ciclo de vida próprio. Quando o código e o diagrama divergirem, atualize o diagrama no mesmo pull request; isso é bem mais fácil quando o diagrama é um link e não uma captura de tela, como descrito em diagramas de arquitetura que não ficam desatualizados.
Monte no Archboard
- Entre com Google ou GitHub e crie um diagrama.
- Do pack UML, adicione uma Classe para cada conceito e escreva atributos e operações, um por linha. Defina o estereótipo de
Lessoncomoabstract. - Adicione uma Interface para
Gradablecom suas duas operações. - Conecte as classes. Defina a ponta de seta do lado do todo como Losango preenchido para composição e Losango para agregação, e como Triângulo do lado do pai para herança. Desenhe a realização como uma linha tracejada com um Triângulo na interface, e a dependência como uma linha tracejada com uma Seta aberta.
- Coloque as multiplicidades como texto ao lado de cada ponta e nomeie as associações, como "teaches", no rótulo da conexão.
- Adicione uma Nota UML para decisões, como por que os papéis são associações.
O exemplo usa 13 nós de diagrama e fica dentro do plano Free. Com um agente de IA conectado via MCP, você pode descrever os requisitos e deixar que ele esboce as classes, e depois corrigir as multiplicidades você mesmo.
Perguntas frequentes
Qual a diferença entre um diagrama de classes e um diagrama de objetos? Um diagrama de classes mostra tipos e as regras entre eles. Um diagrama de objetos mostra instâncias específicas em um momento, com seus valores e vínculos, e serve para conferir um diagrama de classes contra um exemplo real.
Qual a diferença entre agregação e composição? Na composição, uma parte pertence a um único todo e é excluída com ele. Na agregação, as partes podem existir por conta própria e ser compartilhadas; seu significado exato fica a critério do modelador, por isso muitas equipes usam uma associação simples no lugar.
Um diagrama de classes deve corresponder exatamente ao código? Um modelo de domínio deve corresponder aos conceitos e relacionamentos, não a cada campo e método. Mantenha-o num nível que tanto uma gerente de produto quanto um engenheiro consigam ler.
Como um diagrama de classes difere de um diagrama de componentes C4? Um diagrama de componentes C4 mostra blocos implantáveis e suas responsabilidades. Um diagrama de classes desce um nível, até os tipos dentro deles; veja o exemplo de diagrama do modelo C4.
Posso gerar um diagrama de classes automaticamente a partir do código? Muitas IDEs conseguem, mas o resultado mostra todas as classes e é difícil de ler. Desenhe o modelo de domínio à mão ou com um agente de IA e limite-o às classes que importam.