Interfaces Explícitas
Parte de la sección Programación Orientada a Objetos del Journey de C# de Coddy — lección 28 de 70.
Cuando una clase implementa múltiples interfaces que tienen miembros con el mismo nombre, te enfrentas a una colisión de nombres. La implementación explícita de interfaces resuelve esto permitiéndote proporcionar implementaciones separadas para cada interfaz.
Con la implementación explícita, se antepone el nombre de la interfaz al nombre del miembro y se omite el modificador de acceso:
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");
}
}Los miembros implementados explícitamente solo pueden ser accedidos a través de una variable del tipo de interfaz, no a través de la clase misma:
var adapter = new UniversalAdapter();
// adapter.Connect(); // ¡No compilará!
IAmericanPlug american = adapter;
american.Connect(); // Conectado a 120V
IEuropeanPlug european = adapter;
european.Connect(); // Conectado a 220VEsta técnica también es útil cuando desea ocultar miembros de la interfaz de la API pública de la clase. Si un método solo tiene sentido cuando se trata al objeto como una interfaz específica, la implementación explícita mantiene limpia el área de superficie de su clase mientras sigue cumpliendo con el contrato.
Desafío
FácilVamos a construir un sistema generador de informes que demuestre cómo la implementación explícita de interfaces permite que una sola clase proporcione diferentes comportamientos dependiendo de qué interfaz se esté utilizando. Crearás un documento que puede formatearse de manera diferente para su visualización frente a su impresión.
Organizarás tu código en cuatro archivos:
IDisplayable.cs: Define una interfaz llamadaIDisplayableen el espacio de nombresReports. Esta interfaz representa contenido que se puede mostrar en pantalla. Debe declarar un métodoRender()que devuelva un string.IPrintable.cs: Define una interfaz llamadaIPrintableen el espacio de nombresReports. Esta interfaz representa contenido que se puede enviar a una impresora. También debe declarar un métodoRender()que devuelva un string; ¡observa que esto crea una colisión de nombres conIDisplayable!Report.cs: Define una claseReporten el espacio de nombresReportsque implemente tantoIDisplayablecomoIPrintable. El informe debe tener una propiedadTitle(string) y una propiedadContent(string), con un constructor que establezca ambos valores. Utiliza la implementación explícita de interfaces para proporcionar diferentes comportamientos deRender():- Cuando se renderiza para visualización:
"[SCREEN] {Title}: {Content}" - Cuando se renderiza para impresión:
"[PRINT] {Title} | {Content}"
- Cuando se renderiza para visualización:
Program.cs: En tu archivo principal, crea unReportutilizando los valores de entrada. Dado que las implementaciones explícitas solo se pueden acceder a través de variables de interfaz, almacena el mismo informe tanto en una variableIDisplayablecomo en una variableIPrintable, luego llama aRender()en cada una para mostrar cómo el mismo objeto produce diferentes salidas según la interfaz que estés utilizando.
Recibirás dos entradas:
- Título del informe
- Contenido del informe
Imprime la salida en este formato:
Display version:
{Render() via IDisplayable}
Print version:
{Render() via IPrintable}Por ejemplo, si las entradas son Sales Report y Q4 revenue increased by 15%, la salida debería ser:
Display version:
[SCREEN] Sales Report: Q4 revenue increased by 15%
Print version:
[PRINT] Sales Report | Q4 revenue increased by 15%Observa cómo el mismo objeto Report se comporta de manera diferente dependiendo de qué referencia de interfaz utilices para acceder a él. ¡Este es el poder de la implementación explícita de interfaces: resolver colisiones de nombres mientras se proporciona un comportamiento apropiado al contexto!
Hoja de referencia
Cuando una clase implementa múltiples interfaces con miembros que tienen el mismo nombre, puedes usar la implementación explícita de interfaz para proporcionar implementaciones separadas para cada interfaz.
Para implementar explícitamente un miembro de interfaz, antepón el nombre de la interfaz al nombre del miembro y omite el modificador de acceso:
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");
}
}Solo se puede acceder a los miembros implementados explícitamente a través de una variable del tipo de interfaz, no a través de la propia clase:
var adapter = new UniversalAdapter();
// adapter.Connect(); // ¡No compilará!
IAmericanPlug american = adapter;
american.Connect(); // Connected to 120V
IEuropeanPlug european = adapter;
european.Connect(); // Connected to 220VEsta técnica es útil para:
- Resolver colisiones de nombres entre miembros de interfaz
- Ocultar miembros de interfaz de la API pública de la clase cuando solo tienen sentido en el contexto de una interfaz específica
Pruébalo tú mismo
using System;
using Reports;
class Program
{
public static void Main(string[] args)
{
// Leer la entrada
string title = Console.ReadLine();
string content = Console.ReadLine();
// TODO: Crea un objeto Report con el title y content
// TODO: Almacena el informe en una variable IDisplayable
// TODO: Almacena el mismo informe en una variable IPrintable
// TODO: Imprime la versión de visualización llamando a Render() en IDisplayable
Console.WriteLine("Display version:");
// Imprime el resultado del renderizado aquí
// TODO: Imprime la versión de impresión llamando a Render() en IPrintable
Console.WriteLine("Print version:");
// Imprime el resultado del renderizado aquí
}
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de OOP
Archivos externosNamespaces y directivasIntro a clases y objetosLa palabra clave 'this'Métodos y parámetrosCampos vs PropiedadesConstructoresInicializadores de objetosResumen - Calculadora simple4Herencia
Sintaxis básica de herencia (:)La palabra clave 'base'Palabras clave Virtual y OverrideClases selladas (Sealed)La clase base 'object'Resumen: Jerarquía de empleados7Características avanzadas
Sobrecarga de operadoresIndexadores (this[])Sobrescritura de ToString()Métodos de extensiónResumen - Lista personalizada10Patrones de diseño - Parte 1
Introducción a los patrones de diseñoSingleton Thread-SafePatrón FactoryPatrón Observer (Eventos)Patrón Strategy2Propiedades y miembros estáticos
Propiedades autoimplementadasPropiedades de solo lectura y escrituraCampos y métodos estáticosClases estáticasMiembros con cuerpo de expresión5Polimorfismo e Interfaces
Polimorfismo: Compilación vs. EjecuciónInterfaz vs. Clase AbstractaInterfaces MúltiplesInterfaces ExplícitasUpcasting y DowncastingRepaso: Calculadora de Figuras8Conceptos avanzados de POO
Composición sobre herenciaGenéricos (clases y métodos)Delegados y eventosAtributos y ReflexiónIDisposable y la sentencia usingFundamentos de Inyección de dependencias11Patrones de diseño, parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón Composite3Arquitectura de clases
Datos de instancia vs. estáticosPalabras clave 'readonly' y 'const'Campos de respaldoResumen - Gestor de cuentas bancarias6Encapsulamiento
Modificadores de accesoPropiedades para el encapsulamientoImplementación de ocultamiento de datosPatrones de inmutabilidadRepaso - Registros de estudiantes