Classes e objetos: modelando o domínio
Em Programação Orientada a Objetos (POO), uma classe é um molde que define dados (atributos) e comportamentos (métodos). Um objeto é uma instância concreta dessa classe em execução. Em provas, é comum cobrarem a diferença entre “tipo” (classe) e “instância” (objeto), além de identificar responsabilidades bem definidas.
Exemplo conceitual
Em um contexto bancário, “Conta” pode ser uma classe; “Conta do cliente Maria, agência X, número Y” é um objeto. A classe define o que toda conta tem (saldo, número) e faz (depositar, sacar).
Passo a passo prático: do requisito ao modelo
- 1) Liste substantivos do enunciado (candidatos a classes): Conta, Cliente, Transação.
- 2) Liste verbos (candidatos a métodos): sacar, depositar, transferir, registrarTransacao.
- 3) Defina atributos essenciais (evite “tudo que existe”): saldo, numero, agencia.
- 4) Defina invariantes (regras que não podem ser violadas): saldo não pode ficar negativo (dependendo do produto), valor de depósito deve ser positivo.
- 5) Distribua responsabilidades: Conta altera saldo; Transação registra histórico; Cliente agrega contas.
// Pseudocódigo (estilo OO genérico) class Conta { numero agencia saldo metodo depositar(valor) { ... } metodo sacar(valor) { ... } } c1 = new Conta(numero=123, agencia=1) c1.depositar(100)Encapsulamento: protegendo estado e regras
Encapsulamento é o princípio de esconder detalhes internos e expor apenas operações seguras. Em termos práticos: atributos não devem ser alterados diretamente por qualquer parte do sistema; as mudanças passam por métodos que validam regras.
O que a banca costuma explorar
- Diferença entre interface pública (o que é exposto) e implementação (como funciona por dentro).
- Uso de modificadores de acesso (privado/protegido/público) como mecanismo de encapsulamento.
- Evitar “anemia de domínio”: classes que só têm getters/setters e nenhuma regra.
Passo a passo prático: encapsular saldo
- 1) Torne o atributo
saldoinacessível diretamente (privado). - 2) Exponha operações:
depositar,sacar. - 3) Valide entradas e invariantes dentro desses métodos.
- 4) Retorne erros via exceções (ou resultado) quando a regra for violada.
class Conta { privado saldo metodo depositar(valor) { se valor <= 0 entao lancar ExcecaoValorInvalido saldo = saldo + valor } metodo sacar(valor) { se valor <= 0 entao lancar ExcecaoValorInvalido se valor > saldo entao lancar ExcecaoSaldoInsuficiente saldo = saldo - valor } metodo obterSaldo() { retorna saldo } }Herança: generalização e especialização
Herança permite criar uma classe mais específica a partir de outra mais geral, reutilizando atributos e métodos. A classe base define comportamentos comuns; a derivada especializa ou estende.
Quando faz sentido
- Existe relação “é um(a)” (is-a):
ContaPoupancaé umaConta. - Há comportamento comum significativo e estável.
Riscos comuns (cobrados indiretamente)
- Herança usada apenas para “reaproveitar código” pode gerar hierarquias rígidas.
- Quebra de substituição: se a subclasse não puder ser usada no lugar da superclasse sem causar erro conceitual, há problema de design.
class Conta { metodo calcularTarifaMensal() { retorna 0 } } class ContaCorrente herda Conta { metodo calcularTarifaMensal() { retorna 20 } }Polimorfismo: mesma interface, comportamentos diferentes
Polimorfismo é a capacidade de tratar objetos de classes diferentes de forma uniforme por meio de um tipo comum (superclasse ou interface), permitindo que a chamada de um método execute a versão correta conforme o objeto real.
Continue em nosso aplicativo
Você poderá ouvir o audiobook com a tela desligada, ganhar gratuitamente o certificado deste curso e ainda ter acesso a outros 5.000 cursos online gratuitos.
ou continue lendo abaixo...Baixar o aplicativo
Leitura de código: identificando polimorfismo
// Uma lista de Contas pode conter diferentes tipos concretos contas = [new ContaCorrente(), new ContaPoupanca()] totalTarifas = 0 para cada c em contas { totalTarifas = totalTarifas + c.calcularTarifaMensal() // chama a implementação específica } Em prova, isso costuma aparecer como: “qual conceito permite que calcularTarifaMensal() execute versões diferentes sem alterar o laço?” Resposta: polimorfismo (com despacho dinâmico).
Interfaces: contrato de comportamento
Interface define um contrato: quais métodos um tipo deve oferecer, sem impor como serão implementados. É útil para desacoplar código de implementações concretas.
Exemplo: integração com serviços
Em vez de o sistema depender de uma classe específica de notificação, ele depende de uma interface Notificador. Assim, pode-se trocar e-mail por SMS sem alterar regras de negócio.
interface Notificador { metodo enviar(destino, mensagem) } class NotificadorEmail implementa Notificador { metodo enviar(destino, mensagem) { ... } } class NotificadorSMS implementa Notificador { metodo enviar(destino, mensagem) { ... } } class ServicoAlerta { privado notificador: Notificador construtor(n: Notificador) { notificador = n } metodo alertar(msg) { notificador.enviar("cliente", msg) } }Passo a passo prático: aplicar interface para reduzir dependência
- 1) Identifique um ponto de variação (ex.: canal de notificação).
- 2) Extraia um contrato mínimo (método
enviar). - 3) Faça o serviço depender da interface, não da classe concreta.
- 4) Injete a implementação (no construtor, por exemplo).
Composição: montar objetos a partir de outros
Composição é criar classes que possuem outras classes como partes internas (relação “tem um(a)” / has-a). Em muitos cenários, composição é preferível à herança por ser mais flexível.
Exemplo: Conta e Histórico
Uma conta tem um histórico de transações. O histórico pode ser um objeto separado, responsável por armazenar e consultar transações.
class HistoricoTransacoes { privado transacoes = [] metodo registrar(t) { transacoes.adicionar(t) } metodo listar() { retorna transacoes } } class Conta { privado historico = new HistoricoTransacoes() metodo depositar(valor) { ...; historico.registrar("DEP", valor) } }Coesão e acoplamento: qualidade estrutural do design
Coesão (alta é desejável)
Coesão mede o quanto os elementos de um módulo/classe estão relacionados a uma única responsabilidade. Alta coesão significa foco: a classe faz “uma coisa” bem definida.
- Alta coesão:
HistoricoTransacoessó lida com transações. - Baixa coesão: uma classe
Contaque calcula tarifa, envia e-mail, grava em arquivo e valida CPF.
Acoplamento (baixo é desejável)
Acoplamento mede o nível de dependência entre módulos. Baixo acoplamento facilita manutenção e testes, pois mudanças em um módulo impactam menos os outros.
- Acoplamento alto:
ServicoAlertacria internamenteNotificadorEmaile usa detalhes específicos dele. - Acoplamento baixo:
ServicoAlertadepende deNotificador(interface).
Questão típica de prova (interpretação)
“Ao trocar o provedor de notificação, várias classes precisaram ser alteradas. Qual problema de design é mais provável?” Resposta esperada: acoplamento alto (e possível violação de princípios como DIP/OCP).
SOLID em nível objetivo (o que identificar em enunciados)
S — Single Responsibility Principle (SRP)
Uma classe deve ter um único motivo para mudar. Em questões, procure classes “faz-tudo”.
// Sinal de violação de SRP class Conta { metodo sacar(...) { ... } metodo enviarEmail(...) { ... } metodo gerarRelatorioPDF(...) { ... } }O — Open/Closed Principle (OCP)
Entidades devem estar abertas para extensão e fechadas para modificação. Em prova, aparece como “adicionar novo tipo sem alterar código existente”. Interfaces e polimorfismo ajudam.
// Em vez de if/else por tipo, use polimorfismo interface Tarifa { metodo calcular(conta) } class TarifaPadrao implementa Tarifa { ... } class TarifaPremium implementa Tarifa { ... }L — Liskov Substitution Principle (LSP)
Subtipos devem poder substituir seus tipos base sem quebrar expectativas. Indício de violação: subclasse que lança exceções inesperadas para operações válidas na base ou muda significado do método.
I — Interface Segregation Principle (ISP)
Prefira interfaces pequenas e específicas. Indício de violação: interface “gigante” que obriga classes a implementar métodos que não usam.
// Ruim: interface grande interface OperacoesConta { sacar(); depositar(); investir(); contratarSeguro(); } // Melhor: separar por capacidade interface Movimentavel { sacar(); depositar(); } interface Investivel { investir(); }D — Dependency Inversion Principle (DIP)
Dependa de abstrações (interfaces), não de concretizações. Em prova, isso se conecta a injeção de dependência e testabilidade.
Padrões simples: reconhecer Factory e Strategy
Factory (fábrica): centralizar criação
Factory encapsula a lógica de criação de objetos, evitando espalhar new e regras de instanciação pelo sistema. Em questões, aparece como “método que retorna instâncias conforme um parâmetro”.
class ContaFactory { metodo criar(tipo) { se tipo == "CORRENTE" retorna new ContaCorrente() se tipo == "POUPANCA" retorna new ContaPoupanca() senao lancar ExcecaoTipoInvalido } }Strategy (estratégia): variar algoritmo em tempo de execução
Strategy define uma família de algoritmos (ex.: cálculo de tarifa, cálculo de juros), encapsula cada um e permite trocar sem alterar o cliente.
interface RegraTarifa { metodo calcular(conta) } class TarifaIsenta implementa RegraTarifa { ... } class TarifaPadrao implementa RegraTarifa { ... } class Conta { privado regra: RegraTarifa metodo setRegraTarifa(r) { regra = r } metodo tarifaMensal() { retorna regra.calcular(this) } }Tratamento de exceções: falhas previsíveis e fluxo seguro
Exceções representam situações anormais que interrompem o fluxo normal. Em sistemas bancários, validações e regras de negócio frequentemente geram exceções (valor inválido, saldo insuficiente), e integrações podem gerar falhas técnicas (timeout, indisponibilidade).
Conceitos cobrados
- Exceções de negócio: violação de regra (ex.: saque maior que saldo).
- Exceções técnicas: falhas de infraestrutura (ex.: rede, banco indisponível).
- Tratamento: capturar onde é possível decidir (repetir? informar? registrar?).
- Não engolir exceção: capturar e ignorar sem ação dificulta diagnóstico.
Leitura de código: onde tratar
metodo realizarSaque(conta, valor) { tentar { conta.sacar(valor) repositorio.salvar(conta) } capturar (ExcecaoSaldoInsuficiente e) { retornar "Negado: saldo insuficiente" } capturar (ExcecaoInfraestrutura e) { // decisão: reprocessar, registrar, retornar erro genérico registrarLog(e) retornar "Serviço indisponível" } }Coleções: listas, conjuntos e mapas (visão conceitual)
Coleções armazenam e organizam múltiplos objetos. Em prova, é comum cobrarem qual estrutura escolher para cada necessidade e impactos de desempenho e semântica.
Principais tipos e quando usar
- Lista (List): mantém ordem e permite duplicados. Ex.: extrato com transações em ordem cronológica.
- Conjunto (Set): não permite duplicados. Ex.: conjunto de permissões de um usuário.
- Mapa/Dicionário (Map): chave → valor. Ex.: buscar conta por número; buscar cliente por CPF.
Leitura de código: identificar a estrutura
// Lista: extrato transacoes = [] transacoes.adicionar(t1) transacoes.adicionar(t2) // Set: permissões (sem repetição) permissoes = set() permissoes.adicionar("CONSULTAR_SALDO") permissoes.adicionar("CONSULTAR_SALDO") // continua 1 item // Map: conta por número contasPorNumero = map() contasPorNumero[123] = contaA conta = contasPorNumero[123]Leitura de trechos pseudossintáticos: identificando conceitos
Trecho 1
interface Autenticador { metodo autenticar(usuario, senha) } class AutenticadorLDAP implementa Autenticador { ... } class AutenticadorLocal implementa Autenticador { ... } class LoginService { privado auth: Autenticador construtor(a: Autenticador) { auth = a } metodo login(u, s) { retorna auth.autenticar(u, s) } }- Conceitos: interface (contrato), DIP (depender de abstração), baixo acoplamento, possibilidade de Strategy (trocar autenticador).
Trecho 2
class Relatorio { metodo gerarPDF() { ... } metodo enviarEmail() { ... } metodo calcularTarifas() { ... } }- Conceitos: baixa coesão, provável violação de SRP; manutenção difícil (mudanças em e-mail podem afetar PDF).
Trecho 3
class TarifaFactory { metodo obterTarifa(perfil) { se perfil == "BASICO" retorna new TarifaPadrao() se perfil == "VIP" retorna new TarifaIsenta() } }- Conceitos: Factory; pode evoluir para OCP com registro de estratégias para reduzir modificações.
Questões (identificação de conceitos e impactos de design)
1) (Encapsulamento)
Uma classe expõe o atributo saldo como público e permite alteração direta por qualquer módulo. Qual princípio está sendo violado e qual impacto provável?
- Resposta esperada: violação de encapsulamento; impacto: regras de negócio podem ser burladas, maior chance de inconsistência e bugs difíceis de rastrear.
2) (Herança vs composição)
Um sistema criou ContaComHistorico herdando de Conta apenas para adicionar uma lista de transações. Qual alternativa tende a ser melhor e por quê?
- Resposta esperada: composição (Conta tem um Histórico); reduz rigidez da hierarquia e melhora coesão.
3) (Polimorfismo)
Um laço chama calcularTarifaMensal() em objetos de tipos diferentes sem usar if por tipo. Qual conceito permite isso?
- Resposta esperada: polimorfismo.
4) (SOLID — SRP)
Uma classe ClienteService valida CPF, envia e-mail, gera relatório e faz persistência. Qual princípio do SOLID é mais diretamente violado e qual refatoração típica?
- Resposta esperada: SRP; separar em serviços/componentes (validador, notificador, repositório, gerador de relatório).
5) (SOLID — DIP)
Um serviço instancia diretamente new NotificadorEmail() internamente. Qual princípio é afetado e qual melhoria objetiva?
- Resposta esperada: DIP; depender de interface
Notificadore receber a implementação por injeção.
6) (Factory)
Um método central escolhe e cria objetos de tipos diferentes com base em um parâmetro (ex.: “CORRENTE”, “POUPANCA”). Qual padrão está sendo aplicado?
- Resposta esperada: Factory.
7) (Strategy)
O cálculo de tarifa varia conforme o perfil e pode ser trocado sem alterar a classe Conta, apenas substituindo um objeto de regra. Qual padrão é esse e qual benefício?
- Resposta esperada: Strategy; benefício: extensão sem modificar código cliente, melhor testabilidade e aderência ao OCP.
8) (Exceções)
Um código captura uma exceção genérica e não faz nada (nem log, nem retorno). Qual impacto para manutenção e operação?
- Resposta esperada: falhas silenciosas, dificuldade de diagnóstico, comportamento imprevisível e aumento de tempo de correção.
9) (Coleções)
Para buscar rapidamente uma conta pelo número, qual coleção é mais adequada: lista, conjunto ou mapa? Justifique.
- Resposta esperada: mapa/dicionário (chave → valor), pois permite acesso direto por chave.
10) (Coesão e acoplamento)
Ao alterar o provedor de autenticação, várias classes precisam mudar. Isso indica mais fortemente baixa coesão ou alto acoplamento? Qual ação reduz o problema?
- Resposta esperada: alto acoplamento; reduzir dependência de concretos usando interfaces e injeção de dependência.