CodeGym /Cursos /C# SELF /Implementación de múltiples interfaces

Implementación de múltiples interfaces

C# SELF
Nivel 23 , Lección 2
Disponible

1. ¿Por qué implementar varias interfaces?

Cuando diseñas un sistema real, los objetos muchas veces no cumplen solo un "rol" — sino varios a la vez. Imagina: tienes un libro electrónico que no solo puedes leer, sino también editar e incluso guardar en la nube. En términos de programación orientada a objetos (OOP), esto significa que el objeto debe implementar varias interfaces a la vez:

  • IReadable — lectura de contenido.
  • IWritable — edición de contenido.
  • ISyncable — sincronización con almacenamiento remoto.

Aquí es donde entra en juego la posibilidad de implementar varias interfaces. Esto es un superpoder de C# (y de la OOP en general) que no tienen las clases: no puedes heredar de dos o más clases, pero sí puedes implementar todas las interfaces que quieras.

Analogía con la vida real

Imagina a un empleado en una empresa. Vasya puede ser al mismo tiempo:

  • Programador (hace código)
  • Tester (a veces revisa el código de otros)
  • Manager (planifica sprints o la dosis diaria de café)

Estos "roles" tienen tareas completamente diferentes. ¡Pero Vasya se las apaña con cada uno de los contratos! En programación es igual: la clase implementa interfaces y asume sus "responsabilidades".

2. Sintaxis para implementar varias interfaces

Es muy sencillo: al declarar la clase, las pones separadas por comas:


public interface IReadable
{
    void Read();
}

public interface IWritable
{
    void Write(string text);
}

public class Note : IReadable, IWritable
{
    private string content = "";

    public void Read()
    {
        Console.WriteLine("Nota: " + content);
    }

    public void Write(string text)
    {
        content = text;
        Console.WriteLine("¡Nota actualizada!");
    }
}

En este ejemplo, la clase Note implementa ambas interfaces. O sea, está obligada a tener implementaciones de ambos métodos: Read y Write.

3. ¿Cómo se ve esto en el ejemplo de "nuestra" app?

En ejemplos anteriores estuvimos desarrollando una app bancaria sencilla para trabajar con cuentas. Ahora imagina que necesitamos una clase universal para un documento que se pueda imprimir (IPrintable), guardar en un archivo (ISavable) y, quizás, enviar por e-mail (IEmailable).

Definimos las interfaces:


public interface IPrintable
{
    void Print();
}

public interface ISavable
{
    void Save(string filePath);
}

public interface IEmailable
{
    void Email(string toAddress);
}

Clase que lo implementa todo de golpe:


public class Statement : IPrintable, ISavable, IEmailable
{
    public string Content { get; set; }

    public void Print()
    {
        Console.WriteLine("Imprimiendo extracto...");
        Console.WriteLine(Content);
    }

    public void Save(string filePath)
    {
        // Usamos File.WriteAllText de la librería estándar.
        File.WriteAllText(filePath, Content);
        Console.WriteLine($"Extracto guardado en archivo: {filePath}");
    }

    public void Email(string toAddress)
    {
        Console.WriteLine($"Extracto enviado al correo: {toAddress} (simulación)");
    }
}

Ahora puedes usar esta clase en el código como quieras:


var stat = new Statement { Content = "Operaciones del mes: +1000 ud., -500 kd." };
stat.Print();
stat.Save("statement.txt");
stat.Email("boss@bank.corp");

Por cierto, en la práctica es muy cómodo pasar este objeto a métodos que requieren interfaces concretas, sin preocuparte por sus otras capacidades. Por ejemplo, un método de impresión puede recibir un parámetro IPrintable y no tener ni idea de que dentro también hay "guardador" y "correo".

4. Uso de referencias de interfaz: ¿dónde se "ve" lo que sabe hacer mi objeto?

Aquí empieza la magia. Cuando implementas varias interfaces, puedes tratar el objeto solo como uno de sus contratos. Por ejemplo:


IPrintable printable = new Statement { Content = "Lección sobre abstracción" };
printable.Print(); // Solo puedes imprimir

// printable.Save("file.txt"); // Error: la interfaz IPrintable no sabe nada de Save.

Pero si cambiamos a otra interfaz:


ISavable savable = printable as ISavable;
if (savable != null)
{
    savable.Save("file.txt");
}

Esto es útil cuando pasas el objeto a un método que acepta una referencia a una interfaz concreta. Así reduces el acoplamiento y el código es muy flexible: puedes añadir nuevas implementaciones sin tocar el código viejo.

5. Implementación múltiple y métodos iguales en diferentes interfaces

¡Aquí se pone interesante! ¿Qué pasa si dos interfaces requieren un método con el mismo nombre pero distinto significado? Por ejemplo, imagina interfaces para una cafetera:


public interface IStartable
{
    void Start();
}

public interface IRunnable
{
    void Start();
}

La cafetera puede ser tanto "arrancable" (IStartable — inicia el proceso de preparar la bebida), como "runnable" (IRunnable — empieza a funcionar en general).

Implementación por defecto (normal):


public class CoffeeMachine : IStartable, IRunnable
{
    public void Start()
    {
        Console.WriteLine("¡La cafetera arranca para ambos roles!");
    }
}

En este caso, una sola implementación de Start() cubre ambas interfaces.

¿Y si necesitas significados distintos?

Puedes usar implementación explícita de interfaz:


public class CoffeeMachine : IStartable, IRunnable
{
    void IStartable.Start()
    {
        Console.WriteLine("Inicio: ¡preparación de la bebida comenzada!");
    }

    void IRunnable.Start()
    {
        Console.WriteLine("Inicio: la máquina está en modo de trabajo.");
    }
}

En este caso, solo puedes llamar a la implementación concreta a través de una variable de interfaz:


CoffeeMachine cm = new CoffeeMachine();

IStartable startable = cm;
startable.Start(); // Inicio: ¡preparación de la bebida comenzada!

IRunnable runnable = cm;
runnable.Start(); // Inicio: la máquina está en modo de trabajo.

// cm.Start(); // ¡No compila! Start no está disponible como método de la clase.

Por cierto, este truco se usa mucho en la librería estándar de .NET — por ejemplo, cuando una clase implementa varias interfaces parecidas de distintos frameworks y cada una necesita su propio comportamiento.

6. Diferencia entre implementar varias interfaces y heredar

Posibilidad Clase (herencia) Interfaces
Número de tipos base Solo uno Todos los que quieras
Herencia de código Sí (puedes tener implementación base) No, solo signaturas (excepto métodos por defecto)
Almacenar estado No
Añadir nuevo rol No (o complicado, usando composición) Fácil, implementa la interfaz
Mejor para… Ierarqías "físicas" "Roles"/capacidades lógicas

7. Aplicación práctica: objetos "híbridos"

Gracias a la implementación múltiple de interfaces, puedes crear clases con un conjunto único de "roles" sin preocuparte por herencias innecesarias.

Por ejemplo, en nuestra app bancaria — puedes hacer una clase que sepa guardar información sobre sí misma, validarse y también imprimir — todo a través de diferentes interfaces:


public interface IValidatable
{
    bool Validate();
}

public class Check : IPrintable, ISavable, IValidatable
{
    public string Data { get; set; }

    public void Print()
    {
        Console.WriteLine("Imprimiendo cheque: " + Data);
    }

    public void Save(string filePath)
    {
        File.WriteAllText(filePath, Data);
        Console.WriteLine("Cheque guardado: " + filePath);
    }

    public bool Validate()
    {
        return !string.IsNullOrEmpty(Data);
    }
}

Este enfoque te permite construir arquitecturas fácilmente ampliables, donde las clases combinan roles según lo necesites. Si necesitas añadir una nueva "responsabilidad", simplemente implementa una nueva interfaz.

8. Errores típicos y trampas

Un error muy común de los principiantes: olvidar implementar todos los miembros de la interfaz. El compilador aquí no perdona — lanza un error y te dice qué falta. Es fácil de entender: "La clase debe implementar el miembro de la interfaz".

Otro problema frecuente es el lío con la accesibilidad de los métodos. Si implementaste un método de forma explícita, entonces no está disponible a través de la variable de la clase — solo a través de la interfaz.

Otra situación típica es el refactor: haces cambios en la interfaz (por ejemplo, añades un método), pero no actualizas todas las implementaciones. Esto lleva a errores de compilación. Así que cuando diseñes, intenta no cambiar las interfaces después de que muchas clases ya las usen.

Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION