Skip to content

Design Patterns para .NET Senior

📋 Categorias de Padrões

1. Creational Patterns (Padrões Criacionais)

Singleton

Motivo da Criação: O Singleton foi criado para garantir que uma classe tenha apenas uma instância e forneça um ponto de acesso global a essa instância. Isso resolve problemas como controle de acesso a recursos compartilhados (como conexões de banco de dados, configurações globais, ou pools de objetos).

O que faz: O Singleton garante que uma classe tenha apenas uma instância durante toda a execução da aplicação, fornecendo um ponto de acesso global controlado. Ele é útil para gerenciar recursos que devem ser compartilhados por toda a aplicação, como configurações, conexões de banco de dados, ou serviços de logging.

csharp
public sealed class Singleton
{
    private static readonly Lazy<Singleton> _instance = 
        new Lazy<Singleton>(() => new Singleton());
    
    public static Singleton Instance => _instance.Value;
    
    private Singleton() { }
}

Factory

Motivo da Criação: O Factory Pattern foi criado para resolver o problema de criação de objetos complexos, onde a lógica de criação pode ser complicada ou onde você quer desacoplar a criação do uso dos objetos. Ele centraliza a lógica de criação e permite flexibilidade na escolha de implementações.

O que faz: O Factory Pattern encapsula a lógica de criação de objetos, permitindo que subclasses decidam qual classe concreta instanciar. Ele promove o desacoplamento entre a criação e o uso de objetos, facilitando a manutenção e extensão do código.

csharp
public interface IProduct { }
public class ConcreteProductA : IProduct { }
public class ConcreteProductB : IProduct { }

public abstract class Creator
{
    public abstract IProduct FactoryMethod();
}

public class ConcreteCreatorA : Creator
{
    public override IProduct FactoryMethod() => new ConcreteProductA();
}

Builder

Motivo da Criação: O Builder Pattern foi criado para resolver o problema de criação de objetos complexos com muitos parâmetros opcionais. Ele permite construir objetos passo a passo, oferecendo uma API mais fluente e legível, especialmente quando há muitos parâmetros ou quando a ordem de construção é importante.

O que faz: O Builder Pattern separa a construção de um objeto complexo da sua representação, permitindo que o mesmo processo de construção crie diferentes representações. Ele oferece uma API fluente para construir objetos complexos de forma legível e controlada.

csharp
public class ProductBuilder
{
    private Product _product = new();
    
    public ProductBuilder SetName(string name)
    {
        _product.Name = name;
        return this;
    }
    
    public ProductBuilder SetPrice(decimal price)
    {
        _product.Price = price;
        return this;
    }
    
    public Product Build() => _product;
}

Abstract Factory

Motivo da Criação: O Abstract Factory foi criado para resolver o problema de criar famílias de objetos relacionados sem acoplar o código cliente às classes concretas. Ele garante que produtos de uma mesma família sejam usados juntos (por exemplo, UI Windows vs. UI Mac), permitindo trocar a família inteira sem alterar o cliente.

O que faz: O Abstract Factory fornece uma interface para criar famílias de objetos relacionados ou dependentes sem especificar suas classes concretas. O cliente trabalha apenas com abstrações; a fábrica concreta decide quais produtos concretos instanciar.

csharp
public interface IButton { void Render(); }
public interface ICheckbox { void Render(); }

public class WinButton : IButton
{
    public void Render() => Console.WriteLine("Botão Windows");
}

public class MacButton : IButton
{
    public void Render() => Console.WriteLine("Botão Mac");
}

public class WinCheckbox : ICheckbox
{
    public void Render() => Console.WriteLine("Checkbox Windows");
}

public class MacCheckbox : ICheckbox
{
    public void Render() => Console.WriteLine("Checkbox Mac");
}

public interface IUIFactory
{
    IButton CreateButton();
    ICheckbox CreateCheckbox();
}

public class WinFactory : IUIFactory
{
    public IButton CreateButton() => new WinButton();
    public ICheckbox CreateCheckbox() => new WinCheckbox();
}

public class MacFactory : IUIFactory
{
    public IButton CreateButton() => new MacButton();
    public ICheckbox CreateCheckbox() => new MacCheckbox();
}

Prototype

Motivo da Criação: O Prototype foi criado para evitar o custo de criar objetos do zero quando a instanciação é cara ou complexa. Em vez de depender de subclasses ou construtores elaborados, você clona um objeto já configurado (o protótipo) e ajusta apenas o que for necessário.

O que faz: O Prototype permite criar novos objetos copiando (clonando) uma instância existente. Ele desacopla a criação da classe concreta: o cliente solicita um clone sem conhecer detalhes de construção, útil para objetos com estado rico ou configuração custosa.

csharp
public abstract class Prototype
{
    public string Name { get; set; }

    public abstract Prototype Clone();
}

public class ConcretePrototype : Prototype
{
    public string Configuration { get; set; }

    public override Prototype Clone()
    {
        return (Prototype)MemberwiseClone();
    }
}

// Uso
var original = new ConcretePrototype
{
    Name = "Relatório",
    Configuration = "Layout A"
};

var clone = (ConcretePrototype)original.Clone();
clone.Name = "Relatório Cópia";

2. Structural Patterns (Padrões Estruturais)

Adapter

Motivo da Criação: O Adapter Pattern foi criado para resolver problemas de incompatibilidade entre interfaces. Ele permite que classes com interfaces incompatíveis trabalhem juntas, funcionando como uma "tradução" entre diferentes APIs ou bibliotecas.

O que faz: O Adapter Pattern converte a interface de uma classe em outra interface que o cliente espera. Ele permite que classes trabalhem juntas que de outra forma não poderiam devido a interfaces incompatíveis, atuando como uma ponte entre duas interfaces diferentes.

csharp
public interface ITarget
{
    void Request();
}

public class Adaptee
{
    public void SpecificRequest() { }
}

public class Adapter : ITarget
{
    private readonly Adaptee _adaptee;
    
    public Adapter(Adaptee adaptee) => _adaptee = adaptee;
    
    public void Request() => _adaptee.SpecificRequest();
}

Decorator

Motivo da Criação: O Decorator Pattern foi criado para permitir adicionar comportamentos a objetos individuais dinamicamente, sem alterar sua classe. Ele resolve o problema de como adicionar funcionalidades de forma flexível sem usar herança múltipla ou modificar classes existentes.

O que faz: O Decorator Pattern permite adicionar responsabilidades a objetos individuais de forma dinâmica e transparente. Ele oferece uma alternativa flexível à herança para estender funcionalidades, permitindo que comportamentos sejam adicionados ou removidos em tempo de execução.

csharp
public abstract class Component
{
    public abstract string Operation();
}

public class ConcreteComponent : Component
{
    public override string Operation() => "ConcreteComponent";
}

public abstract class Decorator : Component
{
    protected Component _component;
    
    public Decorator(Component component) => _component = component;
    
    public override string Operation() => _component.Operation();
}

Facade

Motivo da Criação: O Facade Pattern foi criado para simplificar interfaces complexas de subsistemas. Ele resolve o problema de como fornecer uma interface simples para um conjunto complexo de classes, bibliotecas ou frameworks, ocultando a complexidade interna.

O que faz: O Facade Pattern fornece uma interface unificada para um conjunto de interfaces em um subsistema. Ele define uma interface de nível mais alto que torna o subsistema mais fácil de usar, encapsulando a complexidade e fornecendo um ponto de entrada único.

csharp
public class Facade
{
    private readonly SubsystemA _a;
    private readonly SubsystemB _b;
    
    public Facade()
    {
        _a = new SubsystemA();
        _b = new SubsystemB();
    }
    
    public string Operation()
    {
        var result = _a.Operation1();
        result += _b.Operation1();
        return result;
    }
}

3. Behavioral Patterns (Padrões Comportamentais)

Observer

Motivo da Criação: O Observer Pattern foi criado para estabelecer uma dependência um-para-muitos entre objetos, onde quando um objeto muda de estado, todos os seus dependentes são notificados automaticamente. Ele resolve o problema de como manter objetos sincronizados sem acoplamento forte.

O que faz: O Observer Pattern define uma dependência um-para-muitos entre objetos, de modo que quando um objeto muda de estado, todos os seus dependentes são notificados e atualizados automaticamente. Ele promove o desacoplamento entre o objeto observado e seus observadores.

csharp
public interface IObserver
{
    void Update(string message);
}

public class Subject
{
    private readonly List<IObserver> _observers = new();
    
    public void Attach(IObserver observer) => _observers.Add(observer);
    public void Detach(IObserver observer) => _observers.Remove(observer);
    
    public void Notify(string message)
    {
        foreach (var observer in _observers)
            observer.Update(message);
    }
}

Strategy

Motivo da Criação: O Strategy Pattern foi criado para permitir que algoritmos sejam selecionados em tempo de execução. Ele resolve o problema de como encapsular algoritmos relacionados em classes separadas e permitir que sejam intercambiáveis sem modificar o código cliente.

O que faz: O Strategy Pattern define uma família de algoritmos, encapsula cada um deles e os torna intercambiáveis. Ele permite que o algoritmo varie independentemente dos clientes que o utilizam, promovendo a flexibilidade e evitando condicionais complexas.

csharp
public interface IStrategy
{
    string AlgorithmInterface();
}

public class ConcreteStrategyA : IStrategy
{
    public string AlgorithmInterface() => "Strategy A";
}

public class Context
{
    private IStrategy _strategy;
    
    public Context(IStrategy strategy) => _strategy = strategy;
    
    public void ContextInterface() => _strategy.AlgorithmInterface();
}

Command

Motivo da Criação: O Command Pattern foi criado para encapsular uma solicitação como um objeto, permitindo parametrizar clientes com diferentes solicitações, enfileirar operações e suportar operações desfazer. Ele resolve o problema de como desacoplar o objeto que invoca uma operação do objeto que sabe como executá-la.

O que faz: O Command Pattern encapsula uma solicitação como um objeto, permitindo parametrizar clientes com diferentes solicitações, enfileirar operações, registrar logs das operações e suportar operações desfazer. Ele promove o desacoplamento entre quem solicita uma operação e quem a executa.

csharp
public interface ICommand
{
    void Execute();
}

public class ConcreteCommand : ICommand
{
    private readonly Receiver _receiver;
    
    public ConcreteCommand(Receiver receiver) => _receiver = receiver;
    
    public void Execute() => _receiver.Action();
}

🏗️ Padrões Arquiteturais

Repository Pattern

Motivo da Criação: O Repository Pattern foi criado para abstrair a lógica de acesso a dados, centralizando operações comuns de CRUD e ocultando a complexidade da persistência de dados. Ele resolve o problema de como separar a lógica de negócio da lógica de acesso a dados.

O que faz: O Repository Pattern atua como uma camada de abstração entre a lógica de negócio e a camada de dados. Ele encapsula a lógica necessária para acessar fontes de dados, centralizando operações comuns de persistência e promovendo a testabilidade e manutenibilidade do código.

csharp
public interface IRepository<T> where T : class
{
    Task<T> GetByIdAsync(int id);
    Task<IEnumerable<T>> GetAllAsync();
    Task AddAsync(T entity);
    Task UpdateAsync(T entity);
    Task DeleteAsync(int id);
}

public class GenericRepository<T> : IRepository<T> where T : class
{
    private readonly DbContext _context;
    
    public GenericRepository(DbContext context) => _context = context;
    
    public async Task<T> GetByIdAsync(int id)
    {
        return await _context.Set<T>().FindAsync(id);
    }
}

Unit of Work Pattern

Motivo da Criação: O Unit of Work Pattern foi criado para gerenciar transações e coordenar o trabalho de múltiplos repositórios. Ele resolve o problema de como garantir consistência de dados quando múltiplas operações de persistência precisam ser executadas como uma única transação.

O que faz: O Unit of Work Pattern coordena o trabalho de múltiplos repositórios, garantindo que todas as operações sejam executadas como uma única transação. Ele mantém uma lista de objetos afetados por uma transação e coordena a escrita das mudanças e a resolução de problemas de concorrência.

csharp
public interface IUnitOfWork : IDisposable
{
    IRepository<T> Repository<T>() where T : class;
    Task<int> SaveChangesAsync();
}

public class UnitOfWork : IUnitOfWork
{
    private readonly DbContext _context;
    private readonly Dictionary<Type, object> _repositories;
    
    public UnitOfWork(DbContext context)
    {
        _context = context;
        _repositories = new Dictionary<Type, object>();
    }
    
    public IRepository<T> Repository<T>() where T : class
    {
        var type = typeof(T);
        if (!_repositories.ContainsKey(type))
            _repositories[type] = new GenericRepository<T>(_context);
        
        return (IRepository<T>)_repositories[type];
    }
}

CQRS Pattern

Motivo da Criação: O CQRS (Command Query Responsibility Segregation) foi criado para separar operações de leitura e escrita, permitindo otimizações específicas para cada tipo de operação. Ele resolve o problema de como escalar aplicações onde as operações de leitura e escrita têm diferentes requisitos de performance e complexidade.

O que faz: O CQRS separa a responsabilidade de comandos (operações de escrita) e consultas (operações de leitura), permitindo que cada lado seja otimizado independentemente. Ele pode usar diferentes modelos de dados, diferentes bancos de dados ou diferentes estratégias de cache para leituras e escritas.

csharp
public interface IQuery<TResponse>
{
}

public interface IQueryHandler<TQuery, TResponse> 
    where TQuery : IQuery<TResponse>
{
    Task<TResponse> HandleAsync(TQuery query);
}

public interface ICommand
{
}

public interface ICommandHandler<TCommand> 
    where TCommand : ICommand
{
    Task HandleAsync(TCommand command);
}

🎯 Padrões Específicos do .NET

Options Pattern

Motivo da Criação: O Options Pattern foi criado para fornecer uma maneira fortemente tipada de acessar configurações de aplicação. Ele resolve o problema de como gerenciar configurações de forma segura e testável, evitando strings mágicas e promovendo a injeção de dependência.

O que faz: O Options Pattern encapsula configurações relacionadas em classes fortemente tipadas, permitindo injeção de dependência e facilitando testes. Ele fornece validação de configuração e permite que diferentes partes da aplicação tenham suas próprias configurações isoladas.

csharp
public class EmailSettings
{
    public string SmtpServer { get; set; }
    public int Port { get; set; }
    public string Username { get; set; }
    public string Password { get; set; }
}

public class EmailService
{
    private readonly EmailSettings _settings;
    
    public EmailService(IOptions<EmailSettings> settings)
    {
        _settings = settings.Value;
    }
}

Middleware Pattern

Motivo da Criação: O Middleware Pattern foi criado para permitir que componentes de software processem requisições e respostas de forma modular. Ele resolve o problema de como adicionar funcionalidades cross-cutting (como logging, autenticação, CORS) de forma reutilizável e configurável.

O que faz: O Middleware Pattern permite que componentes de software processem requisições e respostas em um pipeline configurável. Cada middleware pode processar a requisição, chamar o próximo middleware no pipeline, e processar a resposta, permitindo funcionalidades cross-cutting de forma modular.

csharp
public class CustomMiddleware
{
    private readonly RequestDelegate _next;
    
    public CustomMiddleware(RequestDelegate next) => _next = next;
    
    public async Task InvokeAsync(HttpContext context)
    {
        await _next(context);
    }
}

Pipeline Pattern

Motivo da Criação: O Pipeline Pattern foi criado para processar dados através de uma série de etapas, onde cada etapa pode modificar ou enriquecer os dados. Ele resolve o problema de como processar dados de forma modular e reutilizável, permitindo que diferentes combinações de processamento sejam aplicadas.

O que faz: O Pipeline Pattern processa dados através de uma série de etapas sequenciais, onde cada etapa pode modificar, enriquecer ou validar os dados. Ele promove a reutilização de componentes de processamento e permite que diferentes pipelines sejam construídos com as mesmas etapas.

csharp
public interface IPipelineStep<T>
{
    T Process(T input);
}

public class Pipeline<T>
{
    private readonly List<IPipelineStep<T>> _steps = new();
    
    public Pipeline<T> AddStep(IPipelineStep<T> step)
    {
        _steps.Add(step);
        return this;
    }
    
    public T Execute(T input)
    {
        return _steps.Aggregate(input, (current, step) => step.Process(current));
    }
}

MediatR Pattern

Motivo da Criação: O MediatR Pattern foi criado para implementar o Mediator Pattern de forma simples e eficiente em aplicações .NET. Ele resolve o problema de como desacoplar componentes que precisam se comunicar, reduzindo dependências diretas e facilitando a manutenção e testabilidade.

O que faz: O MediatR implementa o padrão Mediator, permitindo que componentes se comuniquem através de um mediador central sem conhecerem uns aos outros diretamente. Ele suporta comandos (Commands), consultas (Queries) e notificações (Notifications), sendo muito usado com CQRS.

csharp
// Comando
public class CreateUserCommand : IRequest<int>
{
    public string Name { get; set; }
    public string Email { get; set; }
}

public class CreateUserCommandHandler : IRequestHandler<CreateUserCommand, int>
{
    private readonly IUserRepository _repository;
    
    public CreateUserCommandHandler(IUserRepository repository)
    {
        _repository = repository;
    }
    
    public async Task<int> Handle(CreateUserCommand request, CancellationToken cancellationToken)
    {
        var user = new User { Name = request.Name, Email = request.Email };
        return await _repository.CreateAsync(user);
    }
}

// Consulta
public class GetUserQuery : IRequest<User>
{
    public int Id { get; set; }
}

public class GetUserQueryHandler : IRequestHandler<GetUserQuery, User>
{
    private readonly IUserRepository _repository;
    
    public GetUserQueryHandler(IUserRepository repository)
    {
        _repository = repository;
    }
    
    public async Task<User> Handle(GetUserQuery request, CancellationToken cancellationToken)
    {
        return await _repository.GetByIdAsync(request.Id);
    }
}

// Controller
public class UserController : ControllerBase
{
    private readonly IMediator _mediator;
    
    public UserController(IMediator mediator)
    {
        _mediator = mediator;
    }
    
    [HttpPost]
    public async Task<IActionResult> CreateUser(CreateUserCommand command)
    {
        var userId = await _mediator.Send(command);
        return Ok(userId);
    }
    
    [HttpGet("{id}")]
    public async Task<IActionResult> GetUser(int id)
    {
        var user = await _mediator.Send(new GetUserQuery { Id = id });
        return Ok(user);
    }
}

Specification Pattern

Motivo da Criação: O Specification Pattern foi criado para encapsular regras de negócio complexas em objetos reutilizáveis. Ele resolve o problema de como expressar consultas complexas de forma legível e testável, especialmente em consultas de banco de dados.

O que faz: O Specification Pattern encapsula critérios de consulta em objetos que podem ser combinados usando operadores lógicos (AND, OR, NOT). Ele permite que regras de negócio sejam expressas de forma declarativa e reutilizável.

csharp
public interface ISpecification<T>
{
    bool IsSatisfiedBy(T entity);
    Expression<Func<T, bool>> ToExpression();
}

public class ActiveUserSpecification : ISpecification<User>
{
    public bool IsSatisfiedBy(User user)
    {
        return user.IsActive && user.EmailConfirmed;
    }
    
    public Expression<Func<User, bool>> ToExpression()
    {
        return user => user.IsActive && user.EmailConfirmed;
    }
}

public class UserAgeSpecification : ISpecification<User>
{
    private readonly int _minAge;
    
    public UserAgeSpecification(int minAge)
    {
        _minAge = minAge;
    }
    
    public bool IsSatisfiedBy(User user)
    {
        return user.Age >= _minAge;
    }
    
    public Expression<Func<User, bool>> ToExpression()
    {
        return user => user.Age >= _minAge;
    }
}

// Combinação de specifications
public class AndSpecification<T> : ISpecification<T>
{
    private readonly ISpecification<T> _left;
    private readonly ISpecification<T> _right;
    
    public AndSpecification(ISpecification<T> left, ISpecification<T> right)
    {
        _left = left;
        _right = right;
    }
    
    public bool IsSatisfiedBy(T entity)
    {
        return _left.IsSatisfiedBy(entity) && _right.IsSatisfiedBy(entity);
    }
    
    public Expression<Func<T, bool>> ToExpression()
    {
        var leftExpression = _left.ToExpression();
        var rightExpression = _right.ToExpression();
        var parameter = Expression.Parameter(typeof(T));
        
        var combined = Expression.AndAlso(
            Expression.Invoke(leftExpression, parameter),
            Expression.Invoke(rightExpression, parameter)
        );
        
        return Expression.Lambda<Func<T, bool>>(combined, parameter);
    }
}

Unit of Work Pattern

Motivo da Criação: O Unit of Work Pattern foi criado para gerenciar transações de banco de dados de forma consistente e coordenar múltiplos repositórios. Ele resolve o problema de como garantir consistência de dados quando múltiplas operações precisam ser executadas em uma única transação.

O que faz: O Unit of Work Pattern coordena o trabalho de múltiplos repositórios, garantindo que todas as mudanças sejam commitadas ou revertidas como uma única unidade. Ele gerencia o ciclo de vida das transações e promove a consistência de dados.

csharp
public interface IUnitOfWork : IDisposable
{
    IUserRepository Users { get; }
    IOrderRepository Orders { get; }
    Task<int> SaveChangesAsync();
    Task BeginTransactionAsync();
    Task CommitAsync();
    Task RollbackAsync();
}

public class UnitOfWork : IUnitOfWork
{
    private readonly DbContext _context;
    private IDbContextTransaction _transaction;
    
    public UnitOfWork(DbContext context)
    {
        _context = context;
        Users = new UserRepository(context);
        Orders = new OrderRepository(context);
    }
    
    public IUserRepository Users { get; }
    public IOrderRepository Orders { get; }
    
    public async Task<int> SaveChangesAsync()
    {
        return await _context.SaveChangesAsync();
    }
    
    public async Task BeginTransactionAsync()
    {
        _transaction = await _context.Database.BeginTransactionAsync();
    }
    
    public async Task CommitAsync()
    {
        await _transaction?.CommitAsync();
    }
    
    public async Task RollbackAsync()
    {
        await _transaction?.RollbackAsync();
    }
    
    public void Dispose()
    {
        _transaction?.Dispose();
        _context?.Dispose();
    }
}

### Fluent Interface Pattern
**Motivo da Criação**: O Fluent Interface Pattern foi criado para criar APIs mais legíveis e expressivas através de method chaining. Ele resolve o problema de como criar interfaces que sejam mais naturais e fáceis de usar, especialmente para configurações complexas.

**O que faz**: O Fluent Interface Pattern permite que métodos retornem o próprio objeto, permitindo encadeamento de chamadas. Isso cria APIs mais legíveis e expressivas, especialmente úteis para builders e configurações.

```csharp
public class QueryBuilder
{
    private readonly List<string> _selects = new();
    private readonly List<string> _wheres = new();
    private string _from;
    private string _orderBy;
    
    public QueryBuilder Select(string column)
    {
        _selects.Add(column);
        return this;
    }
    
    public QueryBuilder From(string table)
    {
        _from = table;
        return this;
    }
    
    public QueryBuilder Where(string condition)
    {
        _wheres.Add(condition);
        return this;
    }
    
    public QueryBuilder OrderBy(string column)
    {
        _orderBy = column;
        return this;
    }
    
    public string Build()
    {
        var query = $"SELECT {string.Join(", ", _selects)} FROM {_from}";
        if (_wheres.Any())
            query += $" WHERE {string.Join(" AND ", _wheres)}";
        if (!string.IsNullOrEmpty(_orderBy))
            query += $" ORDER BY {_orderBy}";
        return query;
    }
}

// Uso
var query = new QueryBuilder()
    .Select("Name")
    .Select("Email")
    .From("Users")
    .Where("IsActive = 1")
    .Where("Age > 18")
    .OrderBy("Name")
    .Build();

Value Object Pattern

Motivo da Criação: O Value Object Pattern foi criado para representar conceitos do domínio que não têm identidade própria, mas são definidos por seus valores. Ele resolve o problema de como modelar objetos que são imutáveis e comparados por valor.

O que faz: O Value Object Pattern encapsula valores imutáveis que são comparados por seus atributos, não por identidade. Ele promove a imutabilidade e encapsula lógica de negócio relacionada ao valor.

csharp
public class Money : ValueObject
{
    public decimal Amount { get; }
    public string Currency { get; }
    
    public Money(decimal amount, string currency)
    {
        Amount = amount;
        Currency = currency;
    }
    
    public Money Add(Money other)
    {
        if (Currency != other.Currency)
            throw new InvalidOperationException("Cannot add different currencies");
        
        return new Money(Amount + other.Amount, Currency);
    }
    
    public Money Multiply(decimal factor)
    {
        return new Money(Amount * factor, Currency);
    }
    
    protected override IEnumerable<object> GetEqualityComponents()
    {
        yield return Amount;
        yield return Currency;
    }
}

public abstract class ValueObject
{
    protected abstract IEnumerable<object> GetEqualityComponents();
    
    public override bool Equals(object obj)
    {
        if (obj == null || obj.GetType() != GetType())
            return false;
        
        var other = (ValueObject)obj;
        return GetEqualityComponents().SequenceEqual(other.GetEqualityComponents());
    }
    
    public override int GetHashCode()
    {
        return GetEqualityComponents()
            .Select(x => x != null ? x.GetHashCode() : 0)
            .Aggregate((x, y) => x ^ y);
    }
    
    public static bool operator ==(ValueObject left, ValueObject right)
    {
        return EqualOperator(left, right);
    }
    
    public static bool operator !=(ValueObject left, ValueObject right)
    {
        return NotEqualOperator(left, right);
    }
    
    protected static bool EqualOperator(ValueObject left, ValueObject right)
    {
        if (left is null ^ right is null)
            return false;
        return left is null || left.Equals(right);
    }
    
    protected static bool NotEqualOperator(ValueObject left, ValueObject right)
    {
        return !EqualOperator(left, right);
    }
}

Event Sourcing Pattern

Motivo da Criação: O Event Sourcing Pattern foi criado para armazenar todas as mudanças de estado como uma sequência de eventos. Ele resolve o problema de como rastrear mudanças de estado e permitir auditoria completa e reconstrução de estado.

O que faz: O Event Sourcing Pattern armazena todas as mudanças como eventos em um log de eventos. O estado atual é reconstruído aplicando todos os eventos em sequência. Isso permite auditoria completa, time travel e análise de mudanças.

csharp
public abstract class Event
{
    public Guid Id { get; set; } = Guid.NewGuid();
    public DateTime Timestamp { get; set; } = DateTime.UtcNow;
    public string AggregateId { get; set; }
    public long Version { get; set; }
}

public class UserCreatedEvent : Event
{
    public string Name { get; set; }
    public string Email { get; set; }
}

public class UserEmailChangedEvent : Event
{
    public string NewEmail { get; set; }
}

public class UserAggregate
{
    public string Id { get; private set; }
    public string Name { get; private set; }
    public string Email { get; private set; }
    public long Version { get; private set; }
    
    private readonly List<Event> _uncommittedEvents = new();
    
    public UserAggregate(string id, string name, string email)
    {
        Apply(new UserCreatedEvent
        {
            AggregateId = id,
            Name = name,
            Email = email
        });
    }
    
    public void ChangeEmail(string newEmail)
    {
        Apply(new UserEmailChangedEvent
        {
            AggregateId = Id,
            NewEmail = newEmail
        });
    }
    
    private void Apply(Event @event)
    {
        @event.AggregateId = Id;
        @event.Version = Version + 1;
        
        When(@event);
        _uncommittedEvents.Add(@event);
        Version++;
    }
    
    private void When(Event @event)
    {
        switch (@event)
        {
            case UserCreatedEvent e:
                Id = e.AggregateId;
                Name = e.Name;
                Email = e.Email;
                break;
            case UserEmailChangedEvent e:
                Email = e.NewEmail;
                break;
        }
    }
    
    public IEnumerable<Event> GetUncommittedEvents()
    {
        return _uncommittedEvents.AsReadOnly();
    }
    
    public void MarkEventsAsCommitted()
    {
        _uncommittedEvents.Clear();
    }
}

### Saga Pattern
**Motivo da Criação**: O Saga Pattern foi criado para gerenciar transações distribuídas em microservices. Ele resolve o problema de como manter consistência de dados em sistemas distribuídos onde transações tradicionais não são possíveis.

**O que faz**: O Saga Pattern quebra uma transação distribuída em uma série de transações locais, cada uma com uma ação de compensação. Se uma transação falha, as compensações são executadas para reverter as mudanças anteriores.

```csharp
public interface ISagaStep
{
    Task ExecuteAsync();
    Task CompensateAsync();
}

public class CreateOrderSaga
{
    private readonly List<ISagaStep> _steps = new();
    private readonly List<ISagaStep> _executedSteps = new();
    
    public CreateOrderSaga AddStep(ISagaStep step)
    {
        _steps.Add(step);
        return this;
    }
    
    public async Task ExecuteAsync()
    {
        try
        {
            foreach (var step in _steps)
            {
                await step.ExecuteAsync();
                _executedSteps.Add(step);
            }
        }
        catch (Exception)
        {
            await CompensateAsync();
            throw;
        }
    }
    
    private async Task CompensateAsync()
    {
        for (int i = _executedSteps.Count - 1; i >= 0; i--)
        {
            await _executedSteps[i].CompensateAsync();
        }
    }
}

public class ReserveInventoryStep : ISagaStep
{
    private readonly IInventoryService _inventoryService;
    private readonly int _productId;
    private readonly int _quantity;
    
    public ReserveInventoryStep(IInventoryService inventoryService, int productId, int quantity)
    {
        _inventoryService = inventoryService;
        _productId = productId;
        _quantity = quantity;
    }
    
    public async Task ExecuteAsync()
    {
        await _inventoryService.ReserveAsync(_productId, _quantity);
    }
    
    public async Task CompensateAsync()
    {
        await _inventoryService.ReleaseAsync(_productId, _quantity);
    }
}

Circuit Breaker Pattern

Motivo da Criação: O Circuit Breaker Pattern foi criado para prevenir falhas em cascata quando serviços externos estão indisponíveis. Ele resolve o problema de como proteger aplicações de falhas de serviços dependentes e melhorar a resiliência.

O que faz: O Circuit Breaker Pattern monitora falhas e abre o circuito quando o número de falhas excede um limite, evitando chamadas desnecessárias para serviços que estão falhando. Ele pode ser fechado automaticamente após um tempo.

csharp
public enum CircuitBreakerState
{
    Closed,
    Open,
    HalfOpen
}

public class CircuitBreaker
{
    private CircuitBreakerState _state = CircuitBreakerState.Closed;
    private int _failureCount = 0;
    private DateTime _lastFailureTime;
    private readonly int _failureThreshold;
    private readonly TimeSpan _timeout;
    
    public CircuitBreaker(int failureThreshold = 3, int timeoutSeconds = 60)
    {
        _failureThreshold = failureThreshold;
        _timeout = TimeSpan.FromSeconds(timeoutSeconds);
    }
    
    public async Task<T> ExecuteAsync<T>(Func<Task<T>> action)
    {
        if (_state == CircuitBreakerState.Open)
        {
            if (DateTime.UtcNow - _lastFailureTime > _timeout)
            {
                _state = CircuitBreakerState.HalfOpen;
            }
            else
            {
                throw new CircuitBreakerOpenException();
            }
        }
        
        try
        {
            var result = await action();
            OnSuccess();
            return result;
        }
        catch (Exception)
        {
            OnFailure();
            throw;
        }
    }
    
    private void OnSuccess()
    {
        _failureCount = 0;
        _state = CircuitBreakerState.Closed;
    }
    
    private void OnFailure()
    {
        _failureCount++;
        _lastFailureTime = DateTime.UtcNow;
        
        if (_failureCount >= _failureThreshold)
        {
            _state = CircuitBreakerState.Open;
        }
    }
}

public class CircuitBreakerOpenException : Exception
{
    public CircuitBreakerOpenException() : base("Circuit breaker is open") { }
}

Retry Pattern

Motivo da Criação: O Retry Pattern foi criado para lidar com falhas temporárias em operações que podem ser repetidas. Ele resolve o problema de como tornar aplicações mais resilientes a falhas transitórias de rede, banco de dados ou serviços externos.

O que faz: O Retry Pattern automaticamente repete operações que falharam, com estratégias como retry exponencial, backoff e jitter. Ele melhora a confiabilidade de aplicações distribuídas.

csharp
public class RetryPolicy
{
    private readonly int _maxRetries;
    private readonly TimeSpan _baseDelay;
    private readonly Random _random = new();
    
    public RetryPolicy(int maxRetries = 3, int baseDelayMs = 1000)
    {
        _maxRetries = maxRetries;
        _baseDelay = TimeSpan.FromMilliseconds(baseDelayMs);
    }
    
    public async Task<T> ExecuteAsync<T>(Func<Task<T>> operation)
    {
        var lastException = new Exception();
        
        for (int attempt = 0; attempt <= _maxRetries; attempt++)
        {
            try
            {
                return await operation();
            }
            catch (Exception ex) when (IsRetryableException(ex) && attempt < _maxRetries)
            {
                lastException = ex;
                await Task.Delay(CalculateDelay(attempt));
            }
        }
        
        throw lastException;
    }
    
    private bool IsRetryableException(Exception ex)
    {
        return ex is HttpRequestException ||
               ex is TimeoutException ||
               ex is SqlException sqlEx && IsRetryableSqlException(sqlEx);
    }
    
    private bool IsRetryableSqlException(SqlException ex)
    {
        var retryableCodes = new[] { -2, 53, 64, 233, 10053, 10054, 10060, 40197, 40501, 49918, 49919, 49920 };
        return retryableCodes.Contains(ex.Number);
    }
    
    private TimeSpan CalculateDelay(int attempt)
    {
        var delay = _baseDelay * Math.Pow(2, attempt);
        var jitter = delay * _random.NextDouble() * 0.1;
        return TimeSpan.FromMilliseconds(delay.TotalMilliseconds + jitter);
    }
}

📚 Recursos Adicionais

Livros Recomendados

  • "Design Patterns" - Gang of Four
  • "Head First Design Patterns"
  • "Patterns of Enterprise Application Architecture" - Martin Fowler

Artigos e Blogs

  • Microsoft .NET Documentation
  • Martin Fowler's Blog
  • Refactoring Guru

⚠️ Considerações Importantes

  • Não force padrões: Use apenas quando necessário
  • Mantenha simplicidade: Over-engineering é um problema comum
  • Considere performance: Alguns padrões podem impactar performance
  • Documente decisões: Explique por que um padrão foi escolhido
  • Teste adequadamente: Padrões complexos precisam de testes robustos

🚫 Anti-Patterns (Padrões a Evitar)

God Object (Objeto Deus)

O que é: Uma classe que faz muitas coisas e tem muitas responsabilidades.

Problemas:

  • Difícil de manter e testar
  • Alto acoplamento
  • Violação do SRP (Single Responsibility Principle)
  • Dificulta reutilização

Exemplo Ruim:

csharp
public class UserManager
{
    public void CreateUser() { }
    public void UpdateUser() { }
    public void DeleteUser() { }
    public void SendEmail() { }
    public void ValidateData() { }
    public void SaveToDatabase() { }
    public void GenerateReport() { }
    public void ProcessPayment() { }
}

Solução:

csharp
public class UserService
{
    public void CreateUser() { }
    public void UpdateUser() { }
    public void DeleteUser() { }
}

public class EmailService
{
    public void SendEmail() { }
}

public class ValidationService
{
    public void ValidateData() { }
}

Anemic Domain Model (Modelo de Domínio Anêmico)

O que é: Classes que são apenas containers de dados sem comportamento.

Problemas:

  • Lógica de negócio espalhada
  • Violação do encapsulamento
  • Dificulta manutenção
  • Não representa o domínio adequadamente

Exemplo Ruim:

csharp
public class Order
{
    public Guid Id { get; set; }
    public Guid CustomerId { get; set; }
    public List<OrderItem> Items { get; set; }
    public decimal Total { get; set; }
    public OrderStatus Status { get; set; }
}

Solução:

csharp
public class Order
{
    public Guid Id { get; private set; }
    public Guid CustomerId { get; private set; }
    public List<OrderItem> Items { get; private set; }
    public decimal Total => Items.Sum(x => x.Total);
    public OrderStatus Status { get; private set; }
    
    public void AddItem(OrderItem item)
    {
        if (Status != OrderStatus.Draft)
            throw new InvalidOperationException("Cannot add items to confirmed order");
        
        Items.Add(item);
    }
    
    public void Confirm()
    {
        if (Items.Count == 0)
            throw new InvalidOperationException("Cannot confirm empty order");
        
        Status = OrderStatus.Confirmed;
    }
}

Big Ball of Mud (Bola Gigante de Lama)

O que é: Sistema sem estrutura clara, com código espalhado e sem padrões.

Problemas:

  • Extremamente difícil de manter
  • Sem arquitetura definida
  • Código duplicado
  • Impossível de testar adequadamente

Sinais:

  • Funções muito longas
  • Muitas variáveis globais
  • Código duplicado
  • Sem separação de responsabilidades

Spaghetti Code (Código Espaguete)

O que é: Código com fluxo de controle confuso e difícil de seguir.

Problemas:

  • Difícil de entender e manter
  • Muitos níveis de aninhamento
  • Goto statements
  • Lógica complexa e confusa

Exemplo Ruim:

csharp
public void ProcessOrder(Order order)
{
    if (order != null)
    {
        if (order.Items != null)
        {
            if (order.Items.Count > 0)
            {
                foreach (var item in order.Items)
                {
                    if (item != null)
                    {
                        if (item.Quantity > 0)
                        {
                            if (item.Price > 0)
                            {
                                // Processa item
                            }
                        }
                    }
                }
            }
        }
    }
}

Solução:

csharp
public void ProcessOrder(Order order)
{
    if (!IsValidOrder(order))
        return;
    
    foreach (var item in order.Items.Where(IsValidItem))
    {
        ProcessItem(item);
    }
}

private bool IsValidOrder(Order order) =>
    order?.Items?.Any() == true;

private bool IsValidItem(OrderItem item) =>
    item?.Quantity > 0 && item.Price > 0;

Golden Hammer (Martelo de Ouro)

O que é: Uso excessivo de uma tecnologia ou padrão para todos os problemas.

Problemas:

  • Força soluções inadequadas
  • Ignora alternativas melhores
  • Pode criar complexidade desnecessária
  • Limita a evolução do sistema

Exemplos:

  • Usar sempre o mesmo padrão (ex: Repository para tudo)
  • Forçar uma tecnologia específica
  • Ignorar ferramentas mais apropriadas

Vendor Lock-in (Dependência do Provedor)

O que é: Dependência excessiva de tecnologias específicas de um provedor.

Problemas:

  • Difícil migração para outros provedores
  • Custos elevados
  • Limitações técnicas
  • Perda de controle

Estratégias de Mitigação:

  • Usar abstrações e interfaces
  • Implementar padrões de portas e adaptadores
  • Considerar soluções multi-cloud
  • Manter opções de migração

Design Patterns (GoF) - Resumo para Decorar

🏗️ Creational (Criação)

Como criar objetos.

  • Singleton → Garante apenas uma única instância da classe.
  • Factory Method → Delega a criação do objeto para subclasses ou fábricas.
  • Abstract Factory → Cria famílias de objetos relacionados.
  • Builder → Constrói objetos complexos passo a passo.
  • Prototype → Cria novos objetos clonando um existente.

🏛️ Structural (Estruturais)

Como organizar e conectar objetos.

  • Adapter → Converte uma interface em outra compatível.
  • Bridge → Separa abstração da implementação.
  • Composite → Trata objetos individuais e grupos da mesma forma.
  • Decorator → Adiciona funcionalidades sem modificar a classe original.
  • Facade → Fornece uma interface simples para um sistema complexo.
  • Flyweight → Compartilha objetos para economizar memória.
  • Proxy → Controla o acesso ao objeto real.

🤝 Behavioral (Comportamentais)

Como os objetos se comunicam.

  • Chain of Responsibility → Passa a requisição por uma cadeia até alguém tratá-la.
  • Command → Encapsula uma ação em um objeto.
  • Interpreter → Interpreta uma linguagem ou expressão.
  • Iterator → Percorre coleções sem expor sua estrutura.
  • Mediator → Centraliza a comunicação entre objetos.
  • Memento → Salva e restaura o estado de um objeto.
  • Observer → Notifica inscritos quando ocorre uma mudança.
  • State → Altera o comportamento conforme o estado interno.
  • Strategy → Permite trocar o algoritmo em tempo de execução.
  • Template Method → Define a estrutura do algoritmo e deixa partes para subclasses.
  • Visitor → Adiciona operações sem modificar as classes existentes.