Sumário
O que é orientação a objetos?
Orientação a objetos organiza um programa em tipos que representam conceitos e definem operações permitidas sobre seu estado. Um objeto concreto reúne identidade, dados e comportamento; uma classe ou struct define como esses objetos são construídos e o que podem fazer. O objetivo não é espelhar cada substantivo do negócio, e sim manter regras importantes próximas dos dados que elas protegem.
Encapsular estado, definir contratos claros e permitir variações comportamentais são ferramentas. Não são motivos para criar hierarquias ou interfaces sem necessidade. Em C#, a escolha entre uma classe, um tipo por valor, um record ou uma interface muda semântica de identidade, igualdade, cópia, extensão e uso em APIs.
Escolha entre class, struct e record
class define um tipo por referência, com identidade de objeto e herança simples entre classes. struct define um tipo por valor, copiado por valor em atribuições e passagens por valor. record é uma forma de declarar igualdade baseada em valores e oferece sintaxe concisa para dados; pode ser um record class (referência, padrão) ou um record struct (valor).
public sealed class Conta
{
public string Numero { get; }
public decimal Saldo { get; private set; }
}
public readonly record struct Coordenada(double Latitude, double Longitude);
public sealed record Endereco(string Rua, string Cidade);
Uma entidade como uma conta normalmente precisa de identidade estável e regras de transição, o que costuma favorecer uma classe. Uma coordenada pequena e sem identidade própria pode ser um struct imutável. Um endereço ou comando que representa dados e deve comparar pelo conteúdo pode ser um record. O tamanho, a frequência de cópia, o ciclo de vida e os requisitos de igualdade importam mais do que uma regra simplista de “struct é sempre mais rápido”.
Para comparar semântica, herança, igualdade e cenários de uso em detalhe, veja classes, structs e records em C#.
Encapsulamento, abstração e contratos
Encapsulamento protege invariantes: o tipo controla como seu estado pode mudar. Uma propriedade pública com setter irrestrito pode permitir combinações inválidas; um método como Depositar pode validar valor e registrar a transição de forma coesa. Abstração, por sua vez, expõe o que um consumidor precisa saber e omite detalhes que não fazem parte do contrato.
Uma interface representa uma capacidade ou contrato que pode ter implementações diferentes. Uma classe abstrata pode compartilhar estado e implementação parcial entre tipos relacionados. Prefira contratos pequenos e composição quando reduzem acoplamento; não crie uma interface para cada classe por convenção. O artigo de encapsulamento, abstração e interfaces aprofunda essas escolhas.
Herança: especialização com responsabilidade
Herança de classes expressa uma relação “é um” quando o tipo derivado pode respeitar o contrato do tipo base. Uma classe C# pode herdar de uma única classe; pode implementar várias interfaces. Herança permite reutilizar implementação e especializar membros, mas também acopla a subclasse às decisões e invariantes da base. A relação deve ser válida para substituição, não apenas parecer verdadeira no vocabulário.
Use composição para combinar capacidades que variam independentemente ou para reaproveitar um colaborador sem assumir sua hierarquia. O guia de herança em C# mostra virtual, abstract, override, sealed e casos em que composição é mais clara.
Polimorfismo e despacho de métodos
Polimorfismo permite consumir implementações diferentes por um contrato comum. Em tempo de execução, uma chamada virtual ou de interface pode selecionar a implementação concreta; uma sobrecarga, por outro lado, é escolhida pelo compilador com base nos tipos conhecidos naquele ponto. Distinguir esses comportamentos ajuda a entender exatamente qual código será executado.
O modificador override declara que um método substitui um membro virtual ou abstract herdado. Ele não é sinônimo de sobrecarga nem de ocultação com new. Consulte polimorfismo e override em C# para exemplos de despacho, interfaces e armadilhas comuns.
Modificadores de acesso: definir fronteiras
Modificadores como public, private, protected e internal delimitam quem pode usar um tipo ou membro. Tornar tudo público aumenta o contrato que precisa ser mantido; restringir tudo sem considerar os consumidores também pode dificultar extensibilidade e testes. Escolha a menor visibilidade que atende aos usos legítimos.
C# também oferece protected internal, private protected e, desde C# 11, file para tipos de nível superior restritos ao arquivo. As diferenças entre assembly e herança estão explicadas em modificadores de acesso em C#.
Um roteiro para decidir
- Há identidade e ciclo de vida próprios? Considere uma classe.
- O tipo é pequeno, tem semântica de valor clara e cópias previsíveis? Avalie um struct, de preferência imutável.
- Igualdade pelo conteúdo e representação concisa são desejáveis? Avalie um record.
- Uma variação pode ser representada por contrato sem acoplar consumidores a uma hierarquia? Considere interfaces e composição.
- A subclasse sempre respeita as garantias da base? Só então considere herança.
Essas perguntas não substituem o contexto. Revise as escolhas com testes e com as operações reais que o tipo terá de suportar. Os princípios de SOLID ajudam a avaliar responsabilidades, substituição e dependências sem transformar a modelagem em cerimônia.