1. Por que padrões de projeto
Um padrão de projeto é uma solução com nome para um problema recorrente de design de código — não uma biblioteca, mas uma forma de organizar classes que já se provou útil repetidamente. Os cinco abaixo cobrem a maioria dos casos que aparecem num sistema C#/SQL Server típico.
2. Repository — isolando o acesso a dados
O padrão mais relevante para este curso: encapsula toda a lógica de acesso a dados (chamadas às procedures) atrás de uma interface, para que o resto da aplicação nunca precise saber se os dados vêm de SQL Server, de uma API externa ou de um cache.
public interface IClienteRepository
{
Task<Cliente?> ObterPorIdAsync(int id);
Task<List<Cliente>> ListarAsync(string? nome, bool? ativo);
Task<int> InserirAsync(Cliente cliente);
}
public class ClienteRepository : IClienteRepository
{
private readonly string _connectionString;
public ClienteRepository(string connectionString)
=> _connectionString = connectionString;
public async Task<Cliente?> ObterPorIdAsync(int id)
{
await using var conexao = new SqlConnection(_connectionString);
return await conexao.QueryFirstOrDefaultAsync<Cliente>(
"dbo.usp_ClienteObter", new { Id = id },
commandType: CommandType.StoredProcedure);
}
// ObterPorIdAsync, ListarAsync, InserirAsync — todos chamando as
// procedures do Módulo 2 via Dapper, como visto no capítulo 13
}
A classe que usa o repositório depende só da interface
IClienteRepository, nunca de ClienteRepository
diretamente — o que abre caminho para o próximo padrão.
3. Dependency Injection — quem monta as peças não é quem as usa
Em vez de uma classe criar suas próprias dependências com new, elas são
"injetadas" de fora — normalmente pelo construtor. Isso permite trocar a implementação real por
uma de teste sem mudar o código que a utiliza.
public class ClienteService
{
private readonly IClienteRepository _repositorio;
// a dependência chega de fora — ClienteService não sabe (nem precisa saber)
// se é o repositório real ou uma versão de teste
public ClienteService(IClienteRepository repositorio)
=> _repositorio = repositorio;
public async Task<Cliente> ObterOuFalharAsync(int id)
=> await _repositorio.ObterPorIdAsync(id)
?? throw new InvalidOperationException("Cliente não encontrado");
}
Você vai ver Dependency Injection formalizado como parte do próprio framework no Módulo 3 (ASP.NET Core tem um container de DI embutido) — o padrão acima é a base conceitual de tudo aquilo, sem depender de nenhum framework.
4. Singleton — uma única instância compartilhada
public sealed class ConfiguracaoApp
{
private static readonly Lazy<ConfiguracaoApp> _instancia = new(() => new ConfiguracaoApp());
public static ConfiguracaoApp Instancia => _instancia.Value;
public string ConnectionString { get; }
private ConfiguracaoApp() // construtor privado — só a própria classe cria a instância
{
ConnectionString = "Server=...;Database=...;";
}
}
var config = ConfiguracaoApp.Instancia; // sempre o mesmo objeto
Em código ASP.NET Core moderno, você quase nunca escreve um Singleton assim manualmente — o
container de Dependency Injection do framework já gerencia isso com
services.AddSingleton<T>(). Vale conhecer a implementação
manual para entender o que o framework está fazendo por baixo.
5. Factory Method — centralizando a criação de objetos
public interface INotificador
{
Task EnviarAsync(string mensagem);
}
public static class NotificadorFactory
{
public static INotificador Criar(string canal) => canal switch
{
"email" => new NotificadorEmail(),
"sms" => new NotificadorSms(),
_ => throw new ArgumentException($"Canal desconhecido: {canal}")
};
}
INotificador notificador = NotificadorFactory.Criar("email");
await notificador.EnviarAsync("Pedido confirmado!");
Repare o uso da switch expression do capítulo 3 — a fábrica concentra a lógica de "qual classe
concreta criar" num único lugar, e quem chama só conhece a interface INotificador.
6. Strategy — trocando um algoritmo em tempo de execução
Este é o padrão que mais se apoia no que você viu no capítulo anterior: representar um
comportamento intercambiável como um Func/interface, decidido em
tempo de execução.
Dictionary<string, Func<decimal, decimal, decimal>> estrategiasDesconto = new()
{
["percentual"] = (preco, valor) => preco - (preco * valor / 100),
["fixo"] = (preco, valor) => Math.Max(0, preco - valor),
};
decimal AplicarDesconto(decimal preco, string tipo, decimal valor)
=> estrategiasDesconto[tipo](preco, valor);
AplicarDesconto(100m, "percentual", 10m); // 90
AplicarDesconto(100m, "fixo", 15m); // 85
7. Onde isso te leva
Repository e Dependency Injection, em particular, são a espinha dorsal de como o Módulo 3 (MVC) e o Módulo 4 (APIs) organizam código em torno de procedures. O próximo capítulo entra na integração formal com o banco via ADO.NET e Entity Framework Core, aprofundando o que o Módulo 2 já introduziu.