C#: Classes abstratas e interfaces
1. Classes abstratas e métodos abstratos
As classes abstratas são declaradas com o modificador abstract e não podem ser instanciadas. Elas podem conter tanto métodos abstratos quanto métodos concretos. Os métodos abstratos não possuem corpo de método e devem ser sobrescritos nas classes derivadas.
▶ Exemplo
abstract class Shape
{
public string Name { get; set; }
public abstract double Area();
public void Print()
{
Console.WriteLine($"{Name} Area: {Area():F2}");
}
}
class Circle : Shape
{
public double Radius { get; set; }
public Circle(double radius)
{
Name = "Circle";
Radius = radius;
}
public override double Area()
{
return Math.PI * Radius * Radius;
}
}
class Rectangle : Shape
{
public double Width { get; set; }
public double Height { get; set; }
public Rectangle(double w, double h)
{
Name = "Rectangle";
Width = w;
Height = h;
}
public override double Area()
{
return Width * Height;
}
}
Circle c = new Circle(3);
Rectangle r = new Rectangle(4, 5);
c.Print();
r.Print();
Circle Area: 28.27
Rectangle Area: 20.00
💡 Uma classe abstrata é como um “modelo pela metade” — ela especifica o que as subclasses devem completar (métodos abstratos) e também fornece partes que podem ser usadas diretamente (métodos concretos).
2. Classes abstratas x classes comuns
| Característica | Classe regular | Classe abstrata |
|---|---|---|
| Instanciação | Sim | Não |
| Métodos abstratos | Não podem conter | Podem conter |
| Métodos concretos | Sim | Sim |
| Construtor | Sim | Sim |
| Campos | Sim | Sim |
| Quando herdado | Substituição opcional de método virtual | Deve substituir método abstrato |
| modificador selado | Sim | Não (contraditório) |
▶ Exemplo
abstract class Logger
{
protected string prefix;
public Logger(string prefix)
{
this.prefix = prefix;
}
public abstract void Log(string message);
public void LogWithTimestamp(string message)
{
string ts = DateTime.Now.ToString("HH:mm:ss");
Log($"[{ts}] {message}");
}
}
class FileLogger : Logger
{
public FileLogger() : base("FILE") { }
public override void Log(string message)
{
Console.WriteLine($"{prefix}: {message}");
}
}
FileLogger fl = new FileLogger();
fl.Log("Service started");
fl.LogWithTimestamp("Database connected");
FILE: Service started
FILE: [14:30:00] Database connected
⚠️ As classes abstratas podem ter construtores, mas os construtores não podem ser abstract. Os construtores são chamados por meio de base() quando uma subclasse é instanciada.
3. Definição e implementação da interface
As interfaces são definidas por meio da palavra-chave interface e representam um contrato de membros. As classes que implementam uma interface devem fornecer implementações para todos os seus membros. Os membros da interface são implicitamente public e não podem ter modificadores de acesso.
▶ Exemplo
interface IMovable
{
void Move(double dx, double dy);
double Speed { get; set; }
}
interface IDrawable
{
void Draw();
}
class Player : IMovable, IDrawable
{
public double X { get; set; }
public double Y { get; set; }
public double Speed { get; set; }
public Player(double x, double y, double speed)
{
X = x;
Y = y;
Speed = speed;
}
public void Move(double dx, double dy)
{
X += dx * Speed;
Y += dy * Speed;
Console.WriteLine($"Moved to ({X:F1}, {Y:F1})");
}
public void Draw()
{
Console.WriteLine($"Drawing player @ ({X:F1}, {Y:F1})");
}
}
Player p = new Player(0, 0, 2.0);
p.Draw();
p.Move(3, 4);
p.Draw();
Drawing player @ (0.0, 0.0)
Moved to (6.0, 8.0)
Drawing player @ (6.0, 8.0)
📌 Os nomes das interfaces devem começar com uma letra maiúscula I, como IComparable, IDisposable — essa é uma convenção de nomenclatura do C#.
4. Implementação de múltiplas interfaces
Uma classe pode implementar várias interfaces, separadas por vírgulas. Essa é uma das formas pelas quais o C# alcança o polimorfismo, compensando a limitação da herança única.
▶ Exemplo
interface IWalkable
{
void Walk();
}
interface ISwimmable
{
void Swim();
}
interface IFlyable
{
void Fly();
}
class Duck : IWalkable, ISwimmable, IFlyable
{
public string Name { get; set; }
public Duck(string name)
{
Name = name;
}
public void Walk()
{
Console.WriteLine($"{Name} waddles along");
}
public void Swim()
{
Console.WriteLine($"{Name} swims in the water");
}
public void Fly()
{
Console.WriteLine($"{Name} flaps its wings and takes off");
}
}
Duck d = new Duck("Donald");
d.Walk();
d.Swim();
d.Fly();
IWalkable walker = d;
walker.Walk();
ISwimmable swimmer = d;
swimmer.Swim();
Donald waddles along
Donald swims in the water
Donald flaps its wings and takes off
Donald waddles along
Donald swims in the water
💡 Variáveis de diferentes tipos de interface só podem chamar membros definidos por essa interface — isso reflete o Princípio da Segregação de Interfaces.
5. Métodos padrão de interface (C# 8)
A partir do C# 8, as interfaces podem conter métodos com implementações padrão. Isso permite adicionar novos membros a uma interface sem comprometer as classes que já a implementam.
▶ Exemplo
interface ILogger
{
void Log(string message);
void LogWarning(string message)
{
Log($"[WARN] {message}");
}
void LogError(string message)
{
Log($"[ERROR] {message}");
}
}
class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
class SimpleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine($">> {message}");
}
}
ConsoleLogger cl = new ConsoleLogger();
cl.Log("System started");
cl.LogWarning("Low memory");
cl.LogError("Connection failed");
SimpleLogger sl = new SimpleLogger();
sl.Log("System started");
sl.LogWarning("Low memory");
System started
[WARN] Low memory
[ERROR] Connection failed
>> System started
>> [WARN] Low memory
⚠️ Os métodos padrão só estão disponíveis quando chamados por meio do tipo de interface. Eles não podem ser chamados por meio de uma variável do tipo da classe implementadora (a menos que a classe implementadora também reimplemente o método).
6. Guia de seleção entre classe abstrata e interface
| Comparação | Classe abstrata | Interface |
|---|---|---|
| Herança/Implementação | Herança única | Implementação múltipla |
| Tipos de membros | Qualquer | Métodos, propriedades, eventos, indexadores |
| Campos | Sim | Não (antes do C# 8) |
| Construtor | Sim | Não |
| Modificadores de acesso | Qualquer | Padrão: público |
| Implementação padrão | Sim | A partir do C# 8 |
| Compatibilidade entre versões | A adição de membros é segura | A adição de membros causa falhas nas implementações (antes do C# 8) |
| Objetivo do projeto | “O que é” (IS-A) | “O que pode fazer” (CAN-DO) |
Diretrizes de seleção:
- Representa uma relação hierárquica do tipo “é um” → Classe abstrata
- Representa uma capacidade ou comportamento de tipo cruzado → Interface
- Necessidade de compartilhar código e estado → Classe abstrata
- É necessário ter a capacidade de herança múltipla → Interface
- Necessidade de adicionar novos métodos a uma interface existente sem comprometer o código antigo → Método padrão da interface
🔥 Regra rápida: Use classes abstratas para identidade compartilhada e interfaces para capacidade compartilhada.
7. Implementação explícita de interface
Quando várias interfaces possuem membros com o mesmo nome, ou quando for necessário ocultar membros de uma interface, utilize a implementação explícita da interface. A sintaxe é return_type InterfaceName.MemberName. Os membros implementados explicitamente não podem ter modificadores de acesso e só podem ser chamados por meio do tipo da interface.
▶ Exemplo
interface IEnglish
{
void Speak();
}
interface IFrench
{
void Speak();
}
class BilingualPerson : IEnglish, IFrench
{
void IEnglish.Speak()
{
Console.WriteLine("Hello!");
}
void IFrench.Speak()
{
Console.WriteLine("Bonjour!");
}
public void Speak()
{
Console.WriteLine("Hi!");
}
}
BilingualPerson p = new BilingualPerson();
p.Speak();
IEnglish eng = p;
eng.Speak();
IFrench fr = p;
fr.Speak();
Hi!
Hello!
Bonjour!
📌 Os membros de interface implementados explicitamente não são visíveis na instância da classe — é necessário fazer um cast para o tipo de interface correspondente para chamá-los. Isso é tanto uma limitação quanto um mecanismo de encapsulamento.
8. IComparable<T> na prática
IComparable<T> é uma das interfaces mais utilizadas no .NET, empregada para definir a ordem natural de classificação de um tipo. Tanto Array.Sort quanto List<T>.Sort` dependem dessa interface.
▶ Exemplo
class Student : IComparable<Student>
{
public string Name { get; set; }
public int Score { get; set; }
public Student(string name, int score)
{
Name = name;
Score = score;
}
public int CompareTo(Student other)
{
if (other == null) return 1;
return Score.CompareTo(other.Score);
}
public override string ToString()
{
return $"{Name}({Score})";
}
}
Student[] students = new Student[]
{
new Student("Alice", 85),
new Student("Bob", 92),
new Student("Charlie", 78),
new Student("Diana", 95),
new Student("Eve", 88)
};
Console.WriteLine("Before sorting:");
foreach (var s in students)
Console.WriteLine(s);
Array.Sort(students);
Console.WriteLine("After sorting (by score ascending):");
foreach (var s in students)
Console.WriteLine(s);
Before sorting:
Alice(85)
Bob(92)
Charlie(78)
Diana(95)
Eve(88)
After sorting (by score ascending):
Charlie(78)
Alice(85)
Eve(88)
Bob(92)
Diana(95)
💻 Convenção de valor de retorno de CompareTo: um valor negativo significa que o objeto atual vem antes do outro; zero significa que são iguais; e um valor positivo significa que o objeto atual vem depois. Com IComparable<T>, tipos personalizados podem usar diretamente Array.Sort, List<T>.Sort` e outros recursos de classificação.
❓ Perguntas Frequentes
P: Uma classe abstrata pode implementar uma interface? R: Sim. Uma classe abstrata pode implementar uma interface e pode optar por implementar seus membros de forma concreta ou deixá-los como abstratos.
P: Uma interface pode herdar de outra interface? R: Sim. Uma interface pode herdar de várias interfaces, e a classe que a implementa deve implementar todos os membros ao longo da cadeia de herança.
P: Uma implementação explícita de interface pode ter um modificador de acesso público? R: Não. As implementações explícitas de interface não podem ter nenhum modificador de acesso — elas só são acessíveis por meio do tipo de interface.
P: Qual é a diferença entre um método padrão de interface e um método virtual de classe abstrata? R: Os métodos padrão de interface não podem acessar campos de instância, enquanto os métodos virtuais de classe abstrata podem; os métodos padrão são chamados por meio da interface, enquanto os métodos virtuais são chamados por meio de instâncias da classe.
📖 Resumo
- As classes
abstractnão podem ser instanciadas e podem conter tanto métodos abstratos quanto concretos - Os métodos abstratos não têm corpo; as classes derivadas devem sobrescrevê-los com
override - As interfaces definem contratos; as classes de implementação devem fornecer implementações para todos os membros
- Uma classe pode implementar várias interfaces, compensando assim a limitação da herança única
- As interfaces do C# 8 oferecem suporte a métodos padrão para garantir a compatibilidade com versões anteriores
- As classes abstratas representam “o que é”; as interfaces representam “o que pode fazer”
- A implementação explícita da interface resolve conflitos de nomenclatura e só pode ser chamada por meio do tipo da interface
IComparable<T>define a ordenação natural e funciona com os métodosSort
📝 Exercícios
- Defina uma classe abstrata
Vehiclecom um método abstratoStart()e um método concretoStop(); em seguida, crie as subclassesCareBikepara implementá-la - Defina uma interface
ISerializablecom o métodostring Serialize()e uma interfaceIDeserializablecom o métodovoid Deserialize(string data); em seguida, crie uma classe que implemente ambas as interfaces - Defina duas interfaces
IPlayableeIRecordable, ambas com um métodovoid Pause(), e utilize a implementação explícita de interface para diferenciá-las - Crie uma classe
Productque implementeIComparable<Product>para ordenar por preço e teste comArray.Sort - Adicione um método
void PrintHeader(string title)com uma implementação padrão a uma interfaceIPrintablejá existente e verifique se as classes que já implementavam essa interface não são afetadas