Encapsulamento protege invariantes
Uma invariante é uma condição que deve permanecer verdadeira para um objeto válido. Se um pedido não pode ser enviado sem itens, deixar sua lista pública e mutável permite que qualquer chamador viole essa regra. Em vez disso, o tipo deve oferecer operações que validam as mudanças permitidas.
public sealed class Pedido
{
private readonly List _itens = [];
public IReadOnlyCollection<string> Itens => _itens.AsReadOnly();
public bool Enviado { get; private set; }
public void AdicionarItem(string item)
{
ArgumentException.ThrowIfNullOrWhiteSpace(item);
if (Enviado)
throw new InvalidOperationException("Pedido já enviado.");
_itens.Add(item);
}
public void Enviar()
{
if (_itens.Count == 0)
throw new InvalidOperationException("Pedido sem itens.");
Enviado = true;
}
} O estado interno e os pontos de alteração ficam sob controle do tipo. Expor IReadOnlyCollection evita oferecer diretamente uma API de alteração, mas wrappers de leitura não garantem snapshot imutável se a coleção por trás continuar mudando; escolha o contrato de acordo com o uso pretendido.
Propriedades não são sempre campos públicos disfarçados
Uma propriedade com get e set automáticos pode ser apropriada para dados simples. Porém, um setter irrestrito pode permitir transições que o domínio deveria validar. Use acesso mais restrito, init para inicialização controlada ou métodos que expressem ações e regras. Evite getters que executem operações inesperadas ou setters que escondam efeitos colaterais relevantes.
Abstração define o que o consumidor precisa
Uma abstração remove detalhes irrelevantes para determinada chamada, não toda complexidade do sistema. Um serviço pode depender de uma interface para gravar um documento sem conhecer o fornecedor de armazenamento. A interface deve expressar a necessidade do consumidor, e não espelhar todas as funções de uma classe concreta por precaução.
public interface IRepositorioPedidos
{
Task SalvarAsync(Pedido pedido, CancellationToken cancellationToken);
}Uma implementação SQL, em memória ou remota pode cumprir o contrato. O consumidor passa a depender da operação relevante. Isso é útil quando as implementações podem variar, quando uma fronteira arquitetural precisa ser protegida ou quando o contrato pertence naturalmente a mais de um componente.
Interface ou classe abstrata?
Uma interface descreve um contrato/capacidade que pode ser implementado por tipos de linhagens diferentes. Uma classe abstrata é uma classe-base não instanciável, pode manter estado de instância e compartilhar comportamento, e participa da herança simples de classes. Em C# moderno, interfaces podem conter implementações padrão, mas não substituem o estado compartilhado de instância de uma classe base.
Escolha interface quando importa o que o consumidor pode fazer, e classe abstrata quando há uma especialização real e uma implementação comum intencional. Prefira composição quando um comportamento pode ser delegado a uma estratégia ou colaborador independente. Veja herança e composição para custos e limites das hierarquias.
Não confunda esconder detalhes com adicionar cerimônia
Uma interface por classe sem variação, fronteira ou consumidor separado pode apenas aumentar navegação. Da mesma forma, getters/setters privados demais e métodos sem significado de domínio podem dificultar mais do que proteger. O encapsulamento deve tornar os estados inválidos difíceis de representar e as operações válidas fáceis de reconhecer.
Teste as regras pela API pública do tipo quando possível. Isso valida o comportamento que consumidores realmente usam sem acoplar cada teste à organização privada do código. Os modificadores private, protected, internal e outros ajudam a materializar as fronteiras; veja o guia de modificadores de acesso.
Resumo de decisão
- Identifique quais condições precisam continuar verdadeiras.
- Exponha operações que representam transições válidas, em vez de permitir edição arbitrária.
- Defina contratos pequenos alinhados às necessidades de quem consome.
- Use classe abstrata para compartilhar implementação em uma hierarquia verdadeira; interface para capacidades; composição para combinar comportamentos.
- Evite abstrações sem benefício verificável e mantenha detalhes internos substituíveis.
Essas decisões se conectam a responsabilidade única, inversão de dependência e segregação de interfaces dos princípios SOLID.