Ir al contenido
wannacode

Interfaces

En resumen: Una interfaz es un contrato: una lista de métodos sin cuerpo que toda clase que la implementa tiene que escribir; una clase puede implementar varias interfaces.

Problema

Salarios y facturas

A final de mes la empresa paga dos tipos de cosas: los salarios de los empleados y las facturas de los proveedores. Quieres un solo array y un solo bucle: a quién se paga, cuánto, y el total al final.

Un array Employee[] no sirve: una factura no es un empleado. Hacer que herede de Employee está mal. La frase «una factura es un empleado» no es cierta, y además la factura tendría un nombre, un salario y un aumento.

Una clase padre común Payment tampoco sirve. Manager ya tiene una clase padre, Employee, y una clase solo puede tener una. Los empleados y las facturas no tienen nada en común, salvo que se pueden pagar.

La idea

Una interfaz: un contrato «puede hacer»

Una interfaz es una lista de métodos sin cuerpo. Dice qué tiene que saber hacer una clase, pero no cómo:

interface IPayable
{
int AmountDue();
string Payee();
}
Una clase que acepta el contrato implementa la interfaz y escribe cada uno de sus métodos:
class Invoice : IPayable
{
public string Supplier { get; private set; }
public int Amount { get; private set; }
public Invoice(string supplier, int amount)
{
Supplier = supplier;
Amount = amount;
}
public int AmountDue()
{
return Amount;
}
public string Payee()
{
return Supplier;
}
}
Invoice power = new Invoice("Compañía eléctrica", 340);
Console.WriteLine(power.Payee() + ": " + power.AmountDue() + " €");
Salida
Compañía eléctrica: 340 €

Una interfaz se implementa con los mismos dos puntos que la herencia. Los métodos se escriben sin override: no hay nada que sobrescribir, porque la interfaz no tiene versiones propias.

En C# los nombres de las interfaces empiezan por I: IPayable, IComparable, IDisposable. Así class Invoice : IPayable muestra enseguida que los dos puntos son de una interfaz.

Los métodos de una interfaz siempre son public. Son una promesa a todos, así que en la clase también tienen que ser public.

Míralo

Un solo bucle para todo lo que se paga

El empleado también acepta el contrato. Su importe es el salario mensual:

class Employee : IPayable
{
// ... Name, Salary, el constructor, MonthlyPay()
public int AmountDue()
{
return MonthlyPay();
}
public string Payee()
{
return Name;
}
}

Ahora tanto los empleados como las facturas son IPayable. Una interfaz es un tipo: puede ser el tipo de una variable, de un parámetro o de un array.

IPayable[] payments =
{
new Employee("Juan", 2000),
new Manager("María", 3000, 500),
new Invoice("Compañía eléctrica", 340),
new Invoice("Casero", 1800)
};
int total = 0;
foreach (IPayable p in payments)
{
Console.WriteLine(p.Payee() + ": " + p.AmountDue() + " €");
total += p.AmountDue();
}
Console.WriteLine("Total: " + total + " €");
Salida
Juan: 2000 €
María: 3500 €
Compañía eléctrica: 340 €
Casero: 1800 €
Total: 7640 €

El gerente no escribe nada nuevo. De Employee hereda tanto el contrato como AmountDue(). Y AmountDue() llama a MonthlyPay(), que el gerente ha sobrescrito. Por eso María recibe 3500 €.

IPayable[] payments =
{
new Employee("Juan", 2000),
new Manager("María", 3000, 500),
new Invoice("Compañía eléctrica", 340)
};
int total = 0;
foreach (IPayable p in payments)
{
total += p.AmountDue();
}
Console.WriteLine("Total: " + total + " €");
Paso a paso, en palabras
  1. El array es de tipo IPayable. Las flechas apuntan a tres clases, pero solo llegan a su contrato.
  2. total empieza en 0.
  3. p apunta al empleado: AmountDue() devuelve el salario, 2000.
  4. El gerente es un empleado, así que también se le puede pagar. Salario más bono: 3500.
  5. La factura no es un empleado, pero cumple el mismo contrato: 340.
  6. El bucle no conoce las clases. Solo sabe que a cada uno se le puede pagar.

Pruébalo

¿Cuál es el importe?

¿Qué imprimirá el programa? Las clases son de la tarjeta de arriba.

IPayable p = new Manager("María", 3000, 500);
Console.WriteLine(p.AmountDue());
Respuesta

La idea

Una clase padre, muchos contratos

Contabilidad también quiere las facturas en formato CSV: una línea «proveedor;importe» por cada una. Ese es un segundo contrato. Una clase puede implementar todas las interfaces que quiera, separadas por comas:

interface IExportable
{
string ToCsv();
}
class Invoice : IPayable, IExportable
{
// ... como antes
public string ToCsv()
{
return Supplier + ";" + Amount;
}
}
IExportable[] forAccounting = { new Invoice("Compañía eléctrica", 340), new Invoice("Casero", 1800) };
foreach (IExportable e in forAccounting)
{
Console.WriteLine(e.ToCsv());
}
Salida
Compañía eléctrica;340
Casero;1800

Si la clase tiene a la vez una clase padre y interfaces, la clase padre va primero:

class Manager : Employee, IExportable

El primer nombre después de los dos puntos puede ser una clase. Todos los demás son interfaces.

Hay una sola clase padre y muchos contratos. Así, una clase cuya clase padre ya está ocupada puede entrar igualmente en un array común con clases completamente distintas.

Pruébalo

Encuentra el error

Esta factura no compila. ¿Qué línea tiene el error?

Elige la línea con el error.

Pruébalo

¿Qué falta?

class Invoice  IPayable
{
    // ...
}

La idea

¿Clase abstracta o interfaz?

Las dos son clases padre de las que no puedes crear objetos, y las dos ponen obligaciones a las clases de debajo. La diferencia está en lo que dicen:

Clase abstracta Interfaz
Dice «X es un Y» «X puede»
Cuántas por clase una todas las que quieras
Campos y constructor sí no
Código común sí casi nada
  • Una clase abstracta, cuando las clases de debajo son un mismo tipo de cosa y comparten código y datos: las figuras tienen un nombre y una descripción, los empleados tienen un nombre y un salario.
  • Una interfaz, cuando las clases son distintas pero necesitan una capacidad común: un empleado y una factura «se pueden pagar», un pájaro y un avión «pueden volar».
A menudo se usan juntas: Employee es la clase padre del gerente y del becario, y a la vez implementa IPayable.
Más precisamente, para curiosos
  • Desde C# 8 una interfaz puede tener un método por defecto, con cuerpo. Las clases lo reciben ya hecho, y también pueden escribir el suyo. Así puedes añadir un método nuevo a una interfaz sin romper las clases que ya la implementan. Un método así se llama a través de una variable del tipo de la interfaz (IFlyable f), no a través de Bird:
interface IFlyable
{
void Fly();
void Glide()
{
Console.WriteLine("Planea por el aire");
}
}
class Bird : IFlyable
{
public void Fly()
{
Console.WriteLine("El pájaro bate las alas");
}
}
class Plane : IFlyable
{
public void Fly()
{
Console.WriteLine("El avión despega");
}
}
IFlyable[] flyers = { new Bird(), new Plane() };
foreach (IFlyable f in flyers)
{
f.Fly();
f.Glide();
}
Salida
El pájaro bate las alas
Planea por el aire
El avión despega
Planea por el aire
  • Una interfaz no puede tener campos. Puede tener propiedades, pero solo como promesa: int Amount { get; } dice «toda clase tendrá esta propiedad».

Ejercicio

Contratos

Resuelve las tareas en tu editor.

1. Claxon. Una interfaz IHonkable con un método Honk(). SimpleCar del módulo «Clases y objetos» la implementa.

Salida
¡Bip, bip!

2. Datos. Una interfaz IPrintable con un método PrintDetails(). Impleméntala en Person, y haz que Student (una clase hija de Person) la sobrescriba y añada la nota. Pon los dos en un array IPrintable[].

Salida
Nombre: Jorge, edad: 45
Nombre: Elena, edad: 19
Nota media: 5.5

3. Baile. Una interfaz IDanceable con un método Dance(). DanceMove (un nombre y una duración) la implementa.

Salida
Bailo salsa durante 30 segundos

4. DJ. Una clase Song con un título. Una interfaz IPlayable con un método Play(). DJ la implementa y tiene un método PlaySong(Song song) que recuerda la canción y la reproduce. Si todavía no hay canción, Play() lo dice.

Salida
No hay ninguna canción elegida
Reproduzco 'Bohemian Rhapsody'

5. El estanque. Interfaces ISwimmable con Swim() y IFlyable con Fly(). Fish nada. Duck nada y vuela.

Salida
El pez nada.
El pato nada.
El pato vuela.

Más tareas

  • Descuento. Una interfaz IDiscountable con un método que devuelve el precio después del descuento. Impleméntala en Product (10 % de descuento) y en Subscription (25 % de descuento para estudiantes). Calcula el total de un carrito con un producto de 50 € y una suscripción de estudiante de 12 €: 45 € + 9 € = 54 €.
  • CSV para todos. Haz que Employee también implemente IExportable, con una línea «nombre;salario mensual». Pon empleados y facturas en un solo array IExportable[].

Test

Ponte a prueba

1. ¿Cuántas interfaces puede implementar una clase?
2. ¿Compila new IPayable()?
3. Una clase implementa un método de la interfaz sin public. ¿Qué pasa?
4. ¿Escribes override delante de un método que implementa una interfaz?
5. Hay que pagar a los empleados y las facturas en un solo bucle. No comparten código. ¿Qué encaja mejor?

Creado por Ivo Gergov como parte de la campaña de Evo Tech por una educación accesible en programación ·