Professor: Gabriel Soares Baptista
Quando alguém parte direto para o código, vários problemas aparecem cedo:
UML ajuda a reduzir esse ruído porque mostra o sistema por perspectivas diferentes.
Hoje vamos acompanhar um único domínio do começo ao fim.
Usaremos o Pet Shop Amigo Fiel para ligar:
O pet shop oferece:
Hoje, o processo é manual.
Se você precisasse implementar um sistema de agendamento para esse pet shop hoje, começaria pelas telas, pelas tabelas do banco, pelas classes em C++ ou pelas funcionalidades que o negócio realmente precisa?
Vamos evoluir o mesmo sistema nesta ordem:
O Diagrama de Casos de Uso mostra as funcionalidades percebidas pelos atores externos.
Use quando quiser:
Caso de uso não é passo de algoritmo.
Evite modelar como caso de uso:
Prefira metas relevantes para o ator, como:
include e extendEssas relações ajudam a explicar dependência entre casos de uso.
include: incorporação obrigatóriaextend: ampliação opcional ou condicionalEm linguagem simples:
include = isso sempre acontece juntoextend = isso só acontece em alguns casosincludeUse include quando uma funcionalidade:
No Pet Shop:
Agendar Serviço incorpora a verificação de horário disponívelPergunta prática:
o caso principal consegue terminar corretamente sem esse outro?
Se a resposta for não, include é forte candidato.
extendUse extend quando uma funcionalidade:
No Pet Shop:
Aplicar Desconto pode estender Finalizar CobrançaPergunta prática:
o caso base já funciona sozinho, e esse outro só entra em condições específicas?
Se sim, extend é forte candidato.
Nem toda ferramenta representa include e extend com a mesma fidelidade visual.
O mais importante não é decorar sintaxe da ferramenta.
O mais importante é entender a ideia conceitual de incorporar obrigatoriamente ou estender condicionalmente.
include e extendgraph LR
R[Recepcionista]
subgraph Sistema[Pet Shop Amigo Fiel]
UC1([Agendar Serviço])
UC2([Consultar Disponibilidade])
UC3([Finalizar Cobrança])
UC4([Aplicar Desconto])
end
R --> UC1
R --> UC3
UC1 -.-> UC2
UC4 -.-> UC3 graph LR
R[Recepcionista]
subgraph Sistema[Pet Shop Amigo Fiel]
UC1([Agendar Serviço])
UC2([Consultar Disponibilidade])
UC3([Finalizar Cobrança])
UC4([Aplicar Desconto])
end
R --> UC1
R --> UC3
UC1 -.-> UC2
UC4 -.-> UC3 graph LR
R[Recepcionista]
V[Veterinário / Esteticista]
A[Administrador]
subgraph Sistema[Pet Shop Amigo Fiel]
UC1([Cadastrar Cliente])
UC2([Cadastrar Pet])
UC3([Consultar Disponibilidade])
UC4([Agendar Serviço])
UC5([Registrar Atendimento])
UC6([Finalizar Cobrança])
UC7([Emitir Relatório Diário])
end
R --> UC1
R --> UC2
R --> UC3
R --> UC4
V --> UC5
A --> UC7
R --> UC6
UC4 -.->|<include>| UC3 graph LR
R[Recepcionista]
V[Veterinário / Esteticista]
A[Administrador]
subgraph Sistema[Pet Shop Amigo Fiel]
UC1([Cadastrar Cliente])
UC2([Cadastrar Pet])
UC3([Consultar Disponibilidade])
UC4([Agendar Serviço])
UC5([Registrar Atendimento])
UC6([Finalizar Cobrança])
UC7([Emitir Relatório Diário])
end
R --> UC1
R --> UC2
R --> UC3
R --> UC4
V --> UC5
A --> UC7
R --> UC6
UC4 -.->|<include>| UC3 Agendar Serviço depende da consulta de disponibilidadeEste diagrama ainda não mostra:
Ele responde apenas à pergunta: o que o sistema oferece?
Mesmo antes do modelo estrutural, já conseguimos imaginar operações do sistema.
class PetShopSystem {
public:
void cadastrarCliente(const std::string& nome, const std::string& telefone);
void cadastrarPet(int clienteId, const std::string& nomePet, const std::string& especie);
bool consultarDisponibilidade(const std::string& dataHora) const;
int agendarServico(int petId, int servicoId, int funcionarioId, const std::string& dataHora);
void registrarAtendimento(int agendamentoId, const std::string& observacoes);
void finalizarCobranca(int atendimentoId);
};
Casos de uso ajudam a nomear responsabilidades da camada de aplicação.
O Diagrama de Classes mostra:
Se o caso de uso responde o que o sistema faz, o diagrama de classes responde do que o sistema é composto.
Do domínio do Pet Shop, surgem naturalmente:
ClientePetServicoFuncionarioAgendamentoclassDiagram
direction LR
class Cliente {
-int id
-string nome
-string telefone
+adicionarPet(pet)
}
class Pet {
-int id
-string nome
-string especie
-int idade
}
class Servico {
-int id
-string descricao
-double precoBase
}
class Funcionario {
-int id
-string nome
-string especialidade
}
class Agendamento {
-int id
-string dataHora
-string status
+confirmar()
+cancelar()
}
Cliente "1" --> "0..*" Pet : possui
Pet "1" --> "0..*" Agendamento : gera
Agendamento "1" --> "1" Servico : solicita
Agendamento "1" --> "1" Funcionario : atribui classDiagram
direction LR
class Cliente {
-int id
-string nome
-string telefone
+adicionarPet(pet)
}
class Pet {
-int id
-string nome
-string especie
-int idade
}
class Servico {
-int id
-string descricao
-double precoBase
}
class Funcionario {
-int id
-string nome
-string especialidade
}
class Agendamento {
-int id
-string dataHora
-string status
+confirmar()
+cancelar()
}
Cliente "1" --> "0..*" Pet : possui
Pet "1" --> "0..*" Agendamento : gera
Agendamento "1" --> "1" Servico : solicita
Agendamento "1" --> "1" Funcionario : atribui Cliente pode possuir zero ou muitos PetPet pode gerar zero ou muitos AgendamentoAgendamento envolve um ServicoAgendamento envolve um FuncionarioEssa primeira versão simplifica o domínio para ensinar a base sem excesso de detalhe.
#include <memory>
#include <string>
#include <vector>
class Pet;
class Cliente {
private:
int id;
std::string nome;
std::string telefone;
std::vector<std::shared_ptr<Pet>> pets;
public:
Cliente(int id, std::string nome, std::string telefone)
: id(id), nome(std::move(nome)), telefone(std::move(telefone)) {}
void adicionarPet(const std::shared_ptr<Pet>& pet) {
pets.push_back(pet);
}
};
class Pet {
private:
int id;
std::string nome;
std::string especie;
int idade;
public:
Pet(int id, std::string nome, std::string especie, int idade)
: id(id), nome(std::move(nome)), especie(std::move(especie)), idade(idade) {}
};
class Servico {
private:
int id;
std::string descricao;
double precoBase;
public:
Servico(int id, std::string descricao, double precoBase)
: id(id), descricao(std::move(descricao)), precoBase(precoBase) {}
};
class Funcionario {
private:
int id;
std::string nome;
std::string especialidade;
public:
Funcionario(int id, std::string nome, std::string especialidade)
: id(id), nome(std::move(nome)), especialidade(std::move(especialidade)) {}
};
class Agendamento {
private:
int id;
std::string dataHora;
std::string status;
std::shared_ptr<Pet> pet;
std::shared_ptr<Servico> servico;
std::shared_ptr<Funcionario> funcionario;
public:
Agendamento(int id,
std::string dataHora,
std::shared_ptr<Pet> pet,
std::shared_ptr<Servico> servico,
std::shared_ptr<Funcionario> funcionario)
: id(id),
dataHora(std::move(dataHora)),
status("AGENDADO"),
pet(std::move(pet)),
servico(std::move(servico)),
funcionario(std::move(funcionario)) {}
void confirmar() { status = "CONFIRMADO"; }
void cancelar() { status = "CANCELADO"; }
};
A tradução do modelo para C++ fica clara:
std::vectorstd::shared_ptrEsse é o ponto em que o diagrama deixa de ser só desenho e passa a orientar implementação.
Nossa primeira versão ainda mistura duas ideias diferentes:
Por isso, vamos separar Agendamento de Atendimento.
classDiagram
direction LR
class Cliente {
-int id
-string nome
-string telefone
+adicionarPet(pet)
}
class Pet {
-int id
-string nome
-string especie
-int idade
}
class Servico {
-int id
-string descricao
-double precoBase
}
class Funcionario {
-int id
-string nome
-string especialidade
}
class Agendamento {
-int id
-string dataHora
-string status
+confirmar()
+cancelar()
+gerarAtendimento()
}
class Atendimento {
-int id
-string inicio
-string fim
-string observacoes
-double valorCobrado
+encerrar()
}
Cliente "1" --> "0..*" Pet : possui
Pet "1" --> "0..*" Agendamento : agenda
Agendamento "1" --> "1" Servico : solicita
Agendamento "1" --> "1" Funcionario : atribui
Agendamento "1" --> "0..1" Atendimento : gera classDiagram
direction LR
class Cliente {
-int id
-string nome
-string telefone
+adicionarPet(pet)
}
class Pet {
-int id
-string nome
-string especie
-int idade
}
class Servico {
-int id
-string descricao
-double precoBase
}
class Funcionario {
-int id
-string nome
-string especialidade
}
class Agendamento {
-int id
-string dataHora
-string status
+confirmar()
+cancelar()
+gerarAtendimento()
}
class Atendimento {
-int id
-string inicio
-string fim
-string observacoes
-double valorCobrado
+encerrar()
}
Cliente "1" --> "0..*" Pet : possui
Pet "1" --> "0..*" Agendamento : agenda
Agendamento "1" --> "1" Servico : solicita
Agendamento "1" --> "1" Funcionario : atribui
Agendamento "1" --> "0..1" Atendimento : gera Separar Agendamento de Atendimento evita confundir:
Ganhos práticos:
O Diagrama de Sequência mostra interações dinâmicas.
Use quando quiser:
Vamos detalhar o caso de uso Agendar Serviço.
Participantes:
RecepcionistaTelaAgendamentoAgendamentoServiceAgendaRepositoryAgendamentosequenceDiagram
actor R as Recepcionista
participant T as TelaAgendamento
participant S as AgendamentoService
participant Repo as AgendaRepository
participant A as Agendamento
R->>T: informar pet, serviço, funcionário e horário
T->>S: solicitarAgendamento(petId, servicoId, funcionarioId, dataHora)
S->>Repo: verificarDisponibilidade(funcionarioId, dataHora)
Repo-->>S: disponível
S->>A: criar(status = AGENDADO)
S->>Repo: salvar(A)
Repo-->>S: idGerado
S-->>T: agendamentoCriado(idGerado)
T-->>R: exibir confirmação sequenceDiagram
actor R as Recepcionista
participant T as TelaAgendamento
participant S as AgendamentoService
participant Repo as AgendaRepository
participant A as Agendamento
R->>T: informar pet, serviço, funcionário e horário
T->>S: solicitarAgendamento(petId, servicoId, funcionarioId, dataHora)
S->>Repo: verificarDisponibilidade(funcionarioId, dataHora)
Repo-->>S: disponível
S->>A: criar(status = AGENDADO)
S->>Repo: salvar(A)
Repo-->>S: idGerado
S-->>T: agendamentoCriado(idGerado)
T-->>R: exibir confirmação O Diagrama de Classes nos dizia quem existe.
O Diagrama de Sequência nos mostra:
Ele responde melhor à pergunta: como esse cenário acontece no tempo?
class AgendaRepository {
public:
bool verificarDisponibilidade(int funcionarioId, const std::string& dataHora) const;
int salvar(const Agendamento& agendamento);
};
class AgendamentoService {
private:
AgendaRepository& repository;
public:
explicit AgendamentoService(AgendaRepository& repository) : repository(repository) {}
int solicitarAgendamento(int petId,
int servicoId,
int funcionarioId,
const std::string& dataHora) {
if (!repository.verificarDisponibilidade(funcionarioId, dataHora)) {
throw std::runtime_error("Horário indisponível para o funcionário informado");
}
Agendamento agendamento(/* id */ 0,
dataHora,
buscarPet(petId),
buscarServico(servicoId),
buscarFuncionario(funcionarioId));
return repository.salvar(agendamento);
}
private:
std::shared_ptr<Pet> buscarPet(int petId);
std::shared_ptr<Servico> buscarServico(int servicoId);
std::shared_ptr<Funcionario> buscarFuncionario(int funcionarioId);
};
No fluxo acima:
AgendamentoServiceAgendaRepositoryIsso melhora a separação de responsabilidades.
O Diagrama de Atividades mostra fluxo de ações e decisões.
Use quando quiser modelar:
Diferença importante:
flowchart TD
A([Início]) --> B[Recepcionista identifica cliente e pet]
B --> C{Cliente já está cadastrado?}
C -- Não --> D[Cadastrar cliente]
D --> E[Cadastrar pet]
C -- Sim --> F[Consultar dados do pet]
E --> G{Há horário disponível?}
F --> G
G -- Não --> H[Oferecer novo horário]
H --> I{Cliente aceita reagendar?}
I -- Sim --> J[Registrar novo agendamento]
I -- Não --> K[Encerrar solicitação]
G -- Sim --> L[Confirmar serviço e funcionário]
L --> M[Registrar agendamento]
M --> N[No dia do atendimento, confirmar chegada do pet]
N --> O{Pet compareceu?}
O -- Não --> P[Marcar agendamento como cancelado]
O -- Sim --> Q[Iniciar atendimento]
J --> R([Fim])
K --> R
P --> R
Q --> R flowchart TD
A([Início]) --> B[Recepcionista identifica cliente e pet]
B --> C{Cliente já está cadastrado?}
C -- Não --> D[Cadastrar cliente]
D --> E[Cadastrar pet]
C -- Sim --> F[Consultar dados do pet]
E --> G{Há horário disponível?}
F --> G
G -- Não --> H[Oferecer novo horário]
H --> I{Cliente aceita reagendar?}
I -- Sim --> J[Registrar novo agendamento]
I -- Não --> K[Encerrar solicitação]
G -- Sim --> L[Confirmar serviço e funcionário]
L --> M[Registrar agendamento]
M --> N[No dia do atendimento, confirmar chegada do pet]
N --> O{Pet compareceu?}
O -- Não --> P[Marcar agendamento como cancelado]
O -- Sim --> Q[Iniciar atendimento]
J --> R([Fim])
K --> R
P --> R
Q --> R Ele ajuda a enxergar decisões como:
Essas decisões depois viram:
ifvoid confirmarChegadaDoPet(Agendamento& agendamento, bool compareceu) {
if (!compareceu) {
agendamento.cancelar();
return;
}
agendamento.confirmar();
}
O fluxo do processo ajuda a tornar explícitas regras que o código precisa respeitar.
A modelagem orientada a objetos e a modelagem relacional não devem ficar separadas.
Mapeamento direto no nosso exemplo:
Cliente → clientesPet → petsFuncionario → funcionariosServico → servicosAgendamento → agendamentosAtendimento → atendimentosExemplos de tradução:
Cliente 1 -> 0..* Pet → pets recebe cliente_idPet 1 -> 0..* Agendamento → agendamentos recebe pet_idAgendamento 1 -> 1 Servico → agendamentos recebe servico_idAgendamento 1 -> 1 Funcionario → agendamentos recebe funcionario_idAgendamento 1 -> 0..1 Atendimento → atendimentos referencia agendamentoserDiagram
CLIENTES ||--o{ PETS : possui
PETS ||--o{ AGENDAMENTOS : gera
SERVICOS ||--o{ AGENDAMENTOS : classifica
FUNCIONARIOS ||--o{ AGENDAMENTOS : executa
AGENDAMENTOS ||--o| ATENDIMENTOS : origina
CLIENTES {
int id PK
string nome
string telefone
}
PETS {
int id PK
int cliente_id FK
string nome
string especie
int idade
}
SERVICOS {
int id PK
string descricao
decimal preco_base
}
FUNCIONARIOS {
int id PK
string nome
string especialidade
}
AGENDAMENTOS {
int id PK
int pet_id FK
int servico_id FK
int funcionario_id FK
string data_hora
string status
}
ATENDIMENTOS {
int id PK
int agendamento_id FK
string inicio
string fim
string observacoes
decimal valor_cobrado
} erDiagram
CLIENTES ||--o{ PETS : possui
PETS ||--o{ AGENDAMENTOS : gera
SERVICOS ||--o{ AGENDAMENTOS : classifica
FUNCIONARIOS ||--o{ AGENDAMENTOS : executa
AGENDAMENTOS ||--o| ATENDIMENTOS : origina
CLIENTES {
int id PK
string nome
string telefone
}
PETS {
int id PK
int cliente_id FK
string nome
string especie
int idade
}
SERVICOS {
int id PK
string descricao
decimal preco_base
}
FUNCIONARIOS {
int id PK
string nome
string especialidade
}
AGENDAMENTOS {
int id PK
int pet_id FK
int servico_id FK
int funcionario_id FK
string data_hora
string status
}
ATENDIMENTOS {
int id PK
int agendamento_id FK
string inicio
string fim
string observacoes
decimal valor_cobrado
} O diagrama ER é continuação do modelo de classes.
Se a modelagem UML estiver ruim, o banco tende a denunciar isso rapidamente.
struct AgendamentoRecord {
int id;
int petId;
int servicoId;
int funcionarioId;
std::string dataHora;
std::string status;
};
class AgendaRepository {
public:
int salvar(const Agendamento& agendamento);
std::optional<AgendamentoRecord> buscarPorId(int id) const;
};
Aqui vemos duas camadas convivendo:
Até aqui, cada Agendamento possui um único Servico.
Mas e se o pet fizer banho e tosa no mesmo horário?
Nesse caso:
AgendamentoIsso mostra que modelagem é iterativa.
Percurso completo da aula:
Essa sequência evita começar pelo ponto errado.
UML não substitui o código. Ela melhora o código que você vai escrever, reduz ambiguidade e cria uma ponte entre requisito, projeto orientado a objetos e persistência em banco de dados.
Na próxima aula, avançaremos para Arquitetura de Software: Estilos Arquiteturais, com foco em exemplos como MVC. A pergunta central deixará de ser "como modelar o domínio" e passará a ser "como organizar a solução em camadas, componentes e responsabilidades".
Siga para 8 - software architecture architectural styles mvc.