Interface vs Classe Abstrata
Parte da seção Programação Orientada a Objetos do Journey de C# da Coddy — lição 26 de 70.
Tanto interfaces quanto classes abstratas permitem que você defina contratos que outras classes devem seguir, mas elas servem a propósitos diferentes e possuem capacidades distintas.
Uma interface define o que uma classe deve fazer, sem qualquer implementação. É um contrato puro:
public interface IFlyable
{
void Fly();
}
public class Bird : IFlyable
{
public void Fly()
{
Console.WriteLine("Flapping wings");
}
}Uma classe abstrata pode fornecer tanto um contrato quanto uma implementação compartilhada. É um modelo parcial:
public abstract class Vehicle
{
public string Brand { get; set; }
public void StartEngine()
{
Console.WriteLine("Engine started");
}
public abstract void Drive(); // Deve ser implementado
}
public class Car : Vehicle
{
public override void Drive()
{
Console.WriteLine("Driving on roads");
}
}As principais diferenças se resumem às regras de herança e ao propósito.
Uma classe pode implementar múltiplas interfaces, mas herdar de apenas uma classe abstrata.
Use uma interface quando classes não relacionadas precisam compartilhar comportamento (como IFlyable para pássaros e aviões). Use uma classe abstrata quando classes relacionadas compartilham código e estado comuns (como veículos compartilhando um mecanismo de partida de motor).
Classes abstratas podem ter construtores, campos e modificadores de acesso em membros. Interfaces tradicionalmente não podiam, embora o C# moderno permita implementações padrão.
Na dúvida, prefira interfaces para flexibilidade - você sempre pode adicionar uma classe abstrata mais tarde se uma implementação compartilhada se tornar necessária.
Desafio
FácilVamos construir um sistema de player de mídia que demonstra quando usar interfaces versus classes abstratas. Você criará um design onde dispositivos não relacionados compartilham o comportamento de reprodução através de uma interface, enquanto tipos de mídia relacionados compartilham uma implementação comum através de uma classe abstrata.
Você organizará seu código em quatro arquivos:
IPlayable.cs: Defina uma interface chamadaIPlayableno namespaceMedia. Esta interface representa o contrato para qualquer coisa que possa reproduzir mídia - seja um telefone, uma TV ou um player de música dedicado. Ela deve declarar um único métodoPlay()que retorna uma string.MediaFile.cs: Defina uma classe abstrata chamadaMediaFileno namespaceMedia. Isso representa a base compartilhada para todos os arquivos de mídia. Ela deve ter uma propriedadeTitle(string) e uma propriedadeDuration(int, representando segundos). Inclua um construtor que defina ambos os valores. Adicione um método concreto chamadoGetInfo()que retorna"{Title} ({Duration}s)". Também declare um método abstratoGetType()que retorna uma string - cada tipo de mídia definirá que tipo de arquivo ele é.AudioFile.cs: Defina uma classeAudioFileno namespaceMediaque herda deMediaFilee implementaIPlayable. Adicione uma propriedadeArtist(string). O construtor deve aceitar title, duration e artist. SobrescrevaGetType()para retornar"Audio". ImplementePlay()para retornar"Playing audio: {Title} by {Artist}".Program.cs: No seu arquivo principal, crie umAudioFileusando valores de entrada. Demonstre ambos os aspectos da classe: chameGetInfo()eGetType()(herdados da classe abstrata), depois chamePlay()(da interface). Armazene o arquivo de áudio em uma variávelIPlayablee chamePlay()novamente para mostrar o polimorfismo de interface.
Você receberá três entradas:
- Título da música
- Duração em segundos
- Nome do artista
Imprima a saída neste formato:
Info: {GetInfo() result}
Type: {GetType() result}
Direct: {Play() result}
Via Interface: {Play() result from IPlayable variable}Por exemplo, se as entradas forem Bohemian Rhapsody, 354 e Queen, a saída deve ser:
Info: Bohemian Rhapsody (354s)
Type: Audio
Direct: Playing audio: Bohemian Rhapsody by Queen
Via Interface: Playing audio: Bohemian Rhapsody by QueenObserve como o AudioFile se beneficia de ambas as abordagens: ele herda a implementação compartilhada (GetInfo()) da classe abstrata, ao mesmo tempo que cumpre o contrato IPlayable. A classe abstrata fornece reutilização de código para tipos de mídia relacionados, enquanto a interface permite que qualquer classe não relacionada seja "reproduzível" sem compartilhar uma hierarquia de herança.
Folha de consulta
Uma interface define um contrato que as classes devem seguir, sem qualquer implementação:
public interface IFlyable
{
void Fly();
}
public class Bird : IFlyable
{
public void Fly()
{
Console.WriteLine("Flapping wings");
}
}Uma classe abstrata fornece tanto um contrato quanto uma implementação compartilhada:
public abstract class Vehicle
{
public string Brand { get; set; }
public void StartEngine()
{
Console.WriteLine("Engine started");
}
public abstract void Drive(); // Must be implemented
}
public class Car : Vehicle
{
public override void Drive()
{
Console.WriteLine("Driving on roads");
}
}Principais diferenças:
- Uma classe pode implementar múltiplas interfaces, mas herdar de apenas uma classe abstrata
- Use interfaces quando classes não relacionadas precisam compartilhar comportamento
- Use classes abstratas quando classes relacionadas compartilham código e estado comuns
- Classes abstratas podem ter construtores, campos e modificadores de acesso
- Prefira interfaces para flexibilidade
Experimente você mesmo
using System;
using Media;
class Program
{
public static void Main(string[] args)
{
// Ler os valores de entrada
string title = Console.ReadLine();
int duration = Convert.ToInt32(Console.ReadLine());
string artist = Console.ReadLine();
// TODO: Criar um AudioFile usando os valores de entrada
// TODO: Imprimir Info: seguido pelo resultado de GetInfo()
// TODO: Imprimir Type: seguido pelo resultado de GetType()
// TODO: Imprimir Direct: seguido pelo resultado de Play()
// TODO: Armazenar o arquivo de áudio em uma variável IPlayable
// TODO: Imprimir Via Interface: seguido pelo resultado de Play() da variável IPlayable
}
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Fundamentos de POO
Arquivos ExternosNamespaces e DiretivasIntrodução a Classes e ObjetosA Palavra-chave 'this'Métodos e ParâmetrosCampos vs PropriedadesConstrutoresInicializadores de ObjetosRecapitulação - Calculadora Simples4Herança
Sintaxe Básica de Herança (:)A Palavra-chave 'base'Palavras-chave Virtual e OverrideClasses SeladasA Classe Base 'object'Recapitulação - Hierarquia de Funcionários7Recursos Avançados
Sobrecarga de OperadoresIndexadores (this[])Sobrescrita de ToString()Métodos de ExtensãoRecapitulação - Lista Personalizada10Padrões de Projeto Parte 1
Introdução aos Padrões de ProjetoSingleton Thread-SafePadrão FactoryPadrão Observer (Eventos)Padrão Strategy2Propriedades e Membros Estáticos
Propriedades AutoimplementadasPropriedades de Apenas Leitura/EscritaCampos e Métodos EstáticosClasses EstáticasMembros com Corpo de Expressão5Polimorfismo e Interfaces
Polimorfismo: Compilação vs ExecuçãoInterface vs Classe AbstrataMúltiplas InterfacesInterfaces ExplícitasUpcasting e DowncastingRecapitulação - Calculadora de Formas8Conceitos Avançados de POO
Composição sobre HerançaGenerics (Classes e Métodos)Delegates e EventosAtributos e ReflectionIDisposable e a Instrução usingFundamentos de Injeção de Dependência11Padrões de Projeto Parte 2
Padrão CommandPadrão AdapterPadrão DecoratorPadrão Template MethodPadrão StatePadrão Composite3Arquitetura de Classes
Dados de Instância vs EstáticosPalavras-chave 'readonly' e 'const'Campos de SuporteRecapitulação - Gerenciador de Conta Bancária6Encapsulamento
Modificadores de AcessoPropriedades para EncapsulamentoImplementação de Ocultação de DadosPadrões de ImutabilidadeRecapitulação - Registros de Estudantes9Argumentos Variáveis
A Palavra-chave 'params'Parâmetros OpcionaisArgumentos NomeadosSobrecarga de Métodos