Engenharia de Software

Modelagem de Sistemas com UML

Professor: Gabriel Soares Baptista

Por que modelar antes de programar?

Quando alguém parte direto para o código, vários problemas aparecem cedo:

  • classes com responsabilidade demais
  • nomes confusos
  • entendimento diferente entre membros da equipe
  • banco de dados criado sem ligação clara com o domínio

UML ajuda a reduzir esse ruído porque mostra o sistema por perspectivas diferentes.

Ideia central da aula

Hoje vamos acompanhar um único domínio do começo ao fim.

Usaremos o Pet Shop Amigo Fiel para ligar:

  • requisitos
  • UML
  • código C++ orientado a objetos
  • banco de dados relacional

O problema do Pet Shop

O pet shop oferece:

  • banho
  • tosa
  • consulta veterinária

Hoje, o processo é manual.

  • recepcionista anota em papel
  • conflitos de horário acontecem
  • histórico se perde
  • nem sempre está claro quem atenderá cada pet
Reflita

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?

O fio condutor da modelagem

Vamos evoluir o mesmo sistema nesta ordem:

  1. Casos de Uso para entender o que o sistema oferece
  2. Classes para entender quais objetos existem
  3. Sequência para entender como os objetos colaboram
  4. Atividades para entender o fluxo do processo
  5. Banco de Dados para persistir o modelo

Casos de Uso: o que o sistema oferece?

O Diagrama de Casos de Uso mostra as funcionalidades percebidas pelos atores externos.

Use quando quiser:

  • validar escopo
  • conversar com o cliente em alto nível
  • organizar requisitos funcionais
  • separar o que está dentro e fora do sistema

Cuidado importante

Caso de uso não é passo de algoritmo.

Evite modelar como caso de uso:

  • salvar no banco
  • validar telefone
  • buscar cliente por ID

Prefira metas relevantes para o ator, como:

  • agendar serviço
  • cadastrar pet
  • finalizar cobrança

include e extend

Essas relações ajudam a explicar dependência entre casos de uso.

  • include: incorporação obrigatória
  • extend: ampliação opcional ou condicional

Em linguagem simples:

  • include = isso sempre acontece junto
  • extend = isso só acontece em alguns casos

Quando usar include

Use include quando uma funcionalidade:

  • é obrigatória dentro de outra
  • pode ser reutilizada
  • faz parte do fluxo normal

No Pet Shop:

  • Agendar Serviço incorpora a verificação de horário disponível

Pergunta prática:

o caso principal consegue terminar corretamente sem esse outro?

Se a resposta for não, include é forte candidato.

Quando usar extend

Use extend quando uma funcionalidade:

  • não acontece sempre
  • depende de condição
  • complementa um caso base que já faz sentido sozinho

No Pet Shop:

  • Aplicar Desconto pode estender Finalizar Cobrança

Pergunta prática:

o caso base já funciona sozinho, e esse outro só entra em condições específicas?

Se sim, extend é forte candidato.

Observação sobre ferramentas

Nem toda ferramenta representa include e extend com a mesma fidelidade visual.

  • algumas ferramentas UML dão suporte mais explícito
  • outras apenas ajudam a desenhar a relação visualmente

O mais importante não é decorar sintaxe da ferramenta.

O mais importante é entender a ideia conceitual de incorporar obrigatoriamente ou estender condicionalmente.

Exemplo isolado: include e extend

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

Casos de Uso do Pet Shop

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

Lendo o diagrama de casos de uso

  • o Recepcionista participa do fluxo operacional principal
  • o Veterinário ou Esteticista entra quando o atendimento ocorre
  • o Administrador atua em relatórios e supervisão
  • Agendar Serviço depende da consulta de disponibilidade

Este diagrama ainda não mostra:

  • classes
  • tabelas
  • métodos internos

Ele responde apenas à pergunta: o que o sistema oferece?

Casos de Uso e código C++

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.

Classes: que objetos precisam existir?

O Diagrama de Classes mostra:

  • entidades do domínio
  • atributos
  • métodos
  • associações
  • cardinalidades

Se o caso de uso responde o que o sistema faz, o diagrama de classes responde do que o sistema é composto.

Primeira extração de classes

Do domínio do Pet Shop, surgem naturalmente:

  • Cliente
  • Pet
  • Servico
  • Funcionario
  • Agendamento

Primeira versão do Diagrama de Classes

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

Como ler as cardinalidades

  • um Cliente pode possuir zero ou muitos Pet
  • um Pet pode gerar zero ou muitos Agendamento
  • cada Agendamento envolve um Servico
  • cada Agendamento envolve um Funcionario

Essa primeira versão simplifica o domínio para ensinar a base sem excesso de detalhe.

Diagrama de Classes e C++

#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"; }
};

O que o código mostra

A tradução do modelo para C++ fica clara:

  • atributos UML viram membros privados
  • operações UML viram métodos públicos
  • associações para muitos viram std::vector
  • referências entre objetos podem virar std::shared_ptr

Esse é o ponto em que o diagrama deixa de ser só desenho e passa a orientar implementação.

Refinando o domínio

Nossa primeira versão ainda mistura duas ideias diferentes:

  • o compromisso futuro de atender um pet
  • o atendimento efetivamente realizado

Por isso, vamos separar Agendamento de Atendimento.

Diagrama de Classes refinado

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

Por que esse refinamento melhora o modelo?

Separar Agendamento de Atendimento evita confundir:

  • planejamento
  • execução

Ganhos práticos:

  • no código, reduz classes infladas
  • no banco, evita colunas sem sentido antes da execução
  • no domínio, deixa a regra de negócio mais clara

Sequência: como os objetos colaboram?

O Diagrama de Sequência mostra interações dinâmicas.

Use quando quiser:

  • detalhar um cenário específico
  • distribuir responsabilidades
  • verificar a ordem das mensagens
  • testar se a estrutura realmente sustenta o comportamento

Cenário escolhido

Vamos detalhar o caso de uso Agendar Serviço.

Participantes:

  • Recepcionista
  • TelaAgendamento
  • AgendamentoService
  • AgendaRepository
  • Agendamento

Diagrama de Sequência do agendamento

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 que esse diagrama acrescenta?

O Diagrama de Classes nos dizia quem existe.

O Diagrama de Sequência nos mostra:

  • quem chama quem
  • em que ordem
  • quem valida a disponibilidade
  • quem cria o objeto
  • quem persiste os dados

Ele responde melhor à pergunta: como esse cenário acontece no tempo?

Sequência e código C++

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);
};

Decisão importante de projeto

No fluxo acima:

  • a tela não fala diretamente com o banco
  • a regra de negócio fica em AgendamentoService
  • persistência fica em AgendaRepository

Isso melhora a separação de responsabilidades.

Atividades: como o processo flui?

O Diagrama de Atividades mostra fluxo de ações e decisões.

Use quando quiser modelar:

  • processos de negócio
  • ramificações
  • validações
  • caminhos alternativos

Diferença importante:

  • Sequência enfatiza objetos e mensagens
  • Atividades enfatiza ações e decisões

Fluxo do atendimento no Pet Shop

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

O que o Diagrama de Atividades esclarece?

Ele ajuda a enxergar decisões como:

  • cliente já cadastrado?
  • há horário disponível?
  • cliente aceita reagendar?
  • pet compareceu?

Essas decisões depois viram:

  • if
  • validações
  • mudança de estado

Atividades e código C++

void 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.

Do modelo de classes para o banco

A modelagem orientada a objetos e a modelagem relacional não devem ficar separadas.

Mapeamento direto no nosso exemplo:

  • Clienteclientes
  • Petpets
  • Funcionariofuncionarios
  • Servicoservicos
  • Agendamentoagendamentos
  • Atendimentoatendimentos

Cardinalidade e banco relacional

Exemplos de tradução:

  • Cliente 1 -> 0..* Petpets recebe cliente_id
  • Pet 1 -> 0..* Agendamentoagendamentos recebe pet_id
  • Agendamento 1 -> 1 Servicoagendamentos recebe servico_id
  • Agendamento 1 -> 1 Funcionarioagendamentos recebe funcionario_id
  • Agendamento 1 -> 0..1 Atendimentoatendimentos referencia agendamentos

Diagrama ER do Pet Shop

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 que o diagrama ER preserva do UML?

O diagrama ER é continuação do modelo de classes.

  • associações viram chaves estrangeiras
  • cardinalidades viram restrições estruturais
  • entidades persistentes viram tabelas

Se a modelagem UML estiver ruim, o banco tende a denunciar isso rapidamente.

Banco e código C++

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:

  • domínio orientado a objetos
  • persistência relacional

Refinamento importante do domínio

Até aqui, cada Agendamento possui um único Servico.

Mas e se o pet fizer banho e tosa no mesmo horário?

Nesse caso:

  • o Diagrama de Classes precisa rever cardinalidade
  • o banco precisa de tabela associativa
  • o código pode precisar de coleção de serviços em Agendamento

Isso mostra que modelagem é iterativa.

Fechando a progressão

Percurso completo da aula:

  • Casos de Uso → o que o sistema oferece
  • Classes → quais objetos existem
  • Sequência → como colaboram no tempo
  • Atividades → como o processo flui
  • ER → como persistir o modelo

Essa sequência evita começar pelo ponto errado.

Ideia central

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.

Próxima aula

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.