Padrões de Design em C#: Guia Completo com Exemplos
Padrões de design (design patterns) são soluções comprovadas para problemas típicos no desenvolvimento de software. No ecossistema C# e .NET, eles ajudam a criar uma arquitetura flexível, escalável e de fácil manutenção. Neste artigo, vamos analisar quatro padrões principais: Singleton, Factory Method, Observer e Strategy, com exemplos reais em C#.
Por que os padrões de design são necessários em C#?
Os padrões não são bibliotecas ou frameworks prontos. São modelos conceituais que:
- Simplificam a comunicação — desenvolvedores usam uma terminologia unificada
- Melhoram a qualidade do código — reduzem o acoplamento e aumentam a flexibilidade
- Economizam tempo — não é preciso reinventar a roda
- Estão alinhados aos princípios SOLID — a maioria dos padrões implementa esses princípios
1. Singleton (Instância Única)
Finalidade: Garante que uma classe tenha apenas uma instância e fornece um ponto de acesso global a ela.
Quando usar:
- Loggers
- Gerenciadores de configuração
- Pool de conexões com banco de dados
- Caches
Exemplo em C# (implementação thread-safe):
public sealed class ConfigurationManager{ private static readonly Lazy<ConfigurationManager> _instance = new Lazy<ConfigurationManager>(() => new ConfigurationManager());
private readonly Dictionary<string, string> _settings;
private ConfigurationManager() { _settings = new Dictionary<string, string> { { "ConnectionString", "Server=localhost;Database=myDb" }, { "ApiKey", "12345-ABCDE" } }; }
public static ConfigurationManager Instance => _instance.Value;
public string GetSetting(string key) { return _settings.TryGetValue(key, out var value) ? value : null; }}
// Usovar config = ConfigurationManager.Instance;Console.WriteLine(config.GetSetting("ConnectionString"));Vantagens:
- Acesso controlado à instância única
- Inicialização preguiçosa (Lazy<T>)
- Thread-safe sem bloqueios
2. Factory Method (Método Fábrica)
Finalidade: Define uma interface para criar um objeto, mas permite que as subclasses alterem o tipo do objeto criado.
Quando usar:
- Quando o tipo específico do objeto não é conhecido antecipadamente
- Quando a criação do objeto requer lógica complexa
- Para separar o código de criação do código de uso
Exemplo em C#:
// Produto abstratopublic interface ILogger{ void Log(string message);}
// Produtos concretospublic class FileLogger : ILogger{ public void Log(string message) => File.AppendAllText("log.txt", $"{DateTime.Now}: {message}");}
public class ConsoleLogger : ILogger{ public void Log(string message) => Console.WriteLine($"{DateTime.Now}: {message}");}
// Criador com método fábricapublic abstract class LoggerFactory{ public abstract ILogger CreateLogger(); public void LogMessage(string message) { var logger = CreateLogger(); logger.Log(message); }}
public class FileLoggerFactory : LoggerFactory{ public override ILogger CreateLogger() => new FileLogger();}
public class ConsoleLoggerFactory : LoggerFactory{ public override ILogger CreateLogger() => new ConsoleLogger();}
// UsoLoggerFactory factory = new ConsoleLoggerFactory();factory.LogMessage("Olá, mundo!");