Interfaces Explícitas
Parte da seção Programação Orientada a Objetos do Journey de C# da Coddy — lição 28 de 70.
Quando uma classe implementa múltiplas interfaces que possuem membros com o mesmo nome, você enfrenta uma colisão de nomes. A implementação explícita de interface resolve isso permitindo que você forneça implementações separadas para cada interface.
Com a implementação explícita, você prefixa o nome do membro com o nome da interface e omite o modificador de acesso:
public interface IAmericanPlug
{
void Connect();
}
public interface IEuropeanPlug
{
void Connect();
}
public class UniversalAdapter : IAmericanPlug, IEuropeanPlug
{
void IAmericanPlug.Connect()
{
Console.WriteLine("Connected to 120V");
}
void IEuropeanPlug.Connect()
{
Console.WriteLine("Connected to 220V");
}
}Membros implementados explicitamente só podem ser acessados por meio de uma variável do tipo interface, não por meio da própria classe:
var adapter = new UniversalAdapter();
// adapter.Connect(); // Não vai compilar!
IAmericanPlug american = adapter;
american.Connect(); // Conectado a 120V
IEuropeanPlug european = adapter;
european.Connect(); // Conectado a 220VEsta técnica também é útil quando você deseja ocultar membros da interface da API pública da classe. Se um método só faz sentido ao tratar o objeto como uma interface específica, a implementação explícita mantém a área de superfície da sua classe limpa, ao mesmo tempo que cumpre o contrato.
Desafio
FácilVamos construir um sistema gerador de relatórios que demonstra como a implementação explícita de interface permite que uma única classe forneça comportamentos diferentes dependendo de qual interface está sendo usada. Você criará um documento que pode ser formatado de forma diferente para exibição em comparação com a impressão.
Você organizará seu código em quatro arquivos:
IDisplayable.cs: Defina uma interface chamadaIDisplayableno namespaceReports. Esta interface representa o conteúdo que pode ser mostrado na tela. Ela deve declarar um métodoRender()que retorna uma string.IPrintable.cs: Defina uma interface chamadaIPrintableno namespaceReports. Esta interface representa o conteúdo que pode ser enviado para uma impressora. Ela também deve declarar um métodoRender()que retorna uma string - observe que isso cria uma colisão de nomes comIDisplayable!Report.cs: Defina uma classeReportno namespaceReportsque implementa tantoIDisplayablequantoIPrintable. O relatório deve ter uma propriedadeTitle(string) e uma propriedadeContent(string), com um construtor que define ambos os valores. Use a implementação explícita de interface para fornecer diferentes comportamentos deRender():- Quando renderizado para exibição:
"[SCREEN] {Title}: {Content}" - Quando renderizado para impressão:
"[PRINT] {Title} | {Content}"
- Quando renderizado para exibição:
Program.cs: No seu arquivo principal, crie umReportusando os valores de entrada. Como as implementações explícitas só podem ser acessadas através de variáveis de interface, armazene o mesmo relatório em uma variávelIDisplayablee em uma variávelIPrintable, então chameRender()em cada uma para mostrar como o mesmo objeto produz saídas diferentes com base em qual interface você está usando.
Você receberá duas entradas:
- Título do relatório
- Conteúdo do relatório
Imprima a saída neste formato:
Display version:
{Render() via IDisplayable}
Print version:
{Render() via IPrintable}Por exemplo, se as entradas forem Sales Report e Q4 revenue increased by 15%, a saída deve ser:
Display version:
[SCREEN] Sales Report: Q4 revenue increased by 15%
Print version:
[PRINT] Sales Report | Q4 revenue increased by 15%Observe como o mesmo objeto Report se comporta de maneira diferente dependendo de qual referência de interface você usa para acessá-lo. Este é o poder da implementação explícita de interface - resolver colisões de nomes enquanto fornece um comportamento apropriado ao contexto!
Folha de consulta
Quando uma classe implementa múltiplas interfaces com membros que possuem o mesmo nome, você pode usar a implementação explícita de interface para fornecer implementações separadas para cada interface.
Para implementar explicitamente um membro de interface, prefixe o nome do membro com o nome da interface e omita o modificador de acesso:
public interface IAmericanPlug
{
void Connect();
}
public interface IEuropeanPlug
{
void Connect();
}
public class UniversalAdapter : IAmericanPlug, IEuropeanPlug
{
void IAmericanPlug.Connect()
{
Console.WriteLine("Connected to 120V");
}
void IEuropeanPlug.Connect()
{
Console.WriteLine("Connected to 220V");
}
}Membros implementados explicitamente só podem ser acessados através de uma variável do tipo da interface, não através da própria classe:
var adapter = new UniversalAdapter();
// adapter.Connect(); // Não compilará!
IAmericanPlug american = adapter;
american.Connect(); // Connected to 120V
IEuropeanPlug european = adapter;
european.Connect(); // Connected to 220VEsta técnica é útil para:
- Resolver colisões de nomes entre membros de interface
- Ocultar membros de interface da API pública da classe quando eles só fazem sentido em um contexto de interface específico
Experimente você mesmo
using System;
using Reports;
class Program
{
public static void Main(string[] args)
{
// Ler a entrada
string title = Console.ReadLine();
string content = Console.ReadLine();
// TODO: Criar um objeto Report com o title e o content
// TODO: Armazenar o report em uma variável IDisplayable
// TODO: Armazenar o mesmo report em uma variável IPrintable
// TODO: Imprimir a versão de exibição chamando Render() em IDisplayable
Console.WriteLine("Display version:");
// Imprimir o resultado do render aqui
// TODO: Imprimir a versão de impressão chamando Render() em IPrintable
Console.WriteLine("Print version:");
// Imprimir o resultado do render aqui
}
}
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