Está en la página 1de 21

Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

Herencia e Interfaces
Herencia
Introducción

En C# cualquier dato es un objeto porque todos los tipos derivan implícitamente de este
tipo, y “heredan“ los métodos y campos definidos en dicha clase. Cada nuevo tipo tiene
todo lo que tiene el tipo object –y puede utilizarlo e incluso redefinirlo- y añade una
serie de campos y métodos.

Cuando, por ejemplo, se está diseñando una interfaz gráfica, sería absurdo tener que
escribir todo el código para cada ventana cuando muchas de ellas son casi iguales y
realizan tareas parecidas. Lo lógico es “aprovechar” ese código que ya está escrito y
probado y cambiar y añadir lo que sea necesario para ir obteniendo nuevas ventanas
particularizadas. Piense, por ejemplo, en un cuadro de diálogo común. Resulta
coherente escribir un código bastante general que sirva para cualquier ventana y
posteriormente modificarlo –que no sea redimensionable, que tenga un título diferente,
etc-, añadiendo características –nuevos botones, etc- o cambiando algunas de ellas.

La herencia proporciona un mecanismo para definir una nueva clase, a partir de otra
que ya existe, modificándola. La nueva clase que se define, se denomina clase derivada
y la clase de la que se “hereda” se llama clase base.

La clase derivada es la misma clase base a la que se añaden nuevos miembros (campos,
métodos, etc) y/o se redefinen alguno de ellos.

La clase base puede ser a su vez, clase derivada de otra. Cuando hay muchas clases
relacionadas entre sí por el mecanismo de la herencia, se habla de jerarquía de clases.

La herencia proporciona dos grandes ventajas al programador: por un lado, permite la


reutilización de código y por otro permite el polimorfismo de referencias.

Fundamentos de la herencia
Para indicar que una clase hereda de otra, se utiliza el siguiente formato general:

[modificador] class ClaseDerivada : ClaseBase


{
//Cuerpo de la clase
}

La clase derivada “hereda” todos los miembros de la clase base, es decir, tiene todos y
cada uno miembros de la clase base y los que el programador desee añadir.

Ejemplo:

using System;

namespace Herencia
{
public class ClaseBase

1/21
Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

{
public int a;
public int b;
public void Imprimir_ab()
{
Console.WriteLine("a={0},b={1}",a,b);
}
}

public class ClaseDerivada:ClaseBase


{
public int c;
public void Imprimir_c()
{
Console.WriteLine("c={0}",c);
}
public void ImprimirSuma()
{
Console.WriteLine("Suma={0}",a+b+c);
}
}

class HerenciaApp
{
static void Main(string[] args)
{
//Se crean objetos de las clases base y derivada
ClaseBase claseBase=new ClaseBase();
ClaseDerivada claseDerivada=new ClaseDerivada();

//La clase base puede invocar sus miembros desde Main


claseBase.a=11;
claseBase.b=12;

//se imprimen los campos de la clase base


claseBase.Imprimir_ab();

//La clase Derivada tiene como miembros propios


//todos los de la clase base
claseDerivada.a=25;
claseDerivada.b=26;
claseDerivada.c=27;
claseDerivada.Imprimir_ab();

//y los añadidos por el programador


claseDerivada.Imprimir_c();
claseDerivada.ImprimirSuma();
}
}
}

La salida de este programa es:

a=11, b=12
a=25, b=26
c=27
Suma=78

2/21
Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

En este ejemplo se observa que la ClaseDerivada tiene como miembros todos los
miembros que ha definido como propios y todos los miembros que tiene la clase de la
que deriva.

Es decir, su miembros son:

Campos: a, b y c
Métodos: Imprimir_ab(), Imprimir_c(), ImprimirSuma()

Por eso, el objeto claseDerivada puede acceder a los campos a y b e invocar el


método Imprimir_ab()y puede llamar desde un método propio como es
ImprimirSuma()a los campos a y b directamente.

En este caso, se dice que la clase ClaseDerivada deriva de la clase ClaseBase o que la
clase ClaseBase es una superclase (clase padre) de la ClaseDerivada -(clase hija o
subclase).

Control de acceso a los miembros de la clase base


Los modificadores de acceso de los miembros de la superclase y de la subclase definen
el encapsulamiento y el interfaz de la clase.

La forma de acceso a cada uno de los miembros heredados de la clase derivada, depende
de los modificadores de acceso que cada uno de dichos miembros tenga en la clase base.

En el primer ejemplo se ha considerado que todos los miembros fueran públicos para
explicar directamente la herencia. En general, esto no es una buena práctica de
programación. Los datos de una clase deben estar protegidos. Se ha estudiado con
anterioridad cómo es el acceso a los miembros de una clase, dependiendo de cómo esté
definido cada uno de los miembros –public o private-.

El problema que aparece con la herencia es el control de acceso a los miembros de la


clase base por parte de la clase derivada. Dicho de otro modo: ¿puede un método de la
clase derivada acceder a miembros privados de la clase base? Como anteriormente se ha
explicado, no se puede acceder a campos privados desde ninguna clase. Sin embargo,
sería muy conveniente que desde la clase derivada se pudiera llamar a los miembros
privados de la clase base ya que éstos son heredados. Esto es posible con el modificador
de acceso protected. Un dato precedido por este modificador es público para
cualquier clase derivada pero privado para el resto de las clases.

Es acceso a los datos se puede resumir esquemáticamente en estos tres puntos:

a) Un miembro de la clase base declarado como public, pasa a ser un miembro


public en la clase derivada, es decir, puede ser utilizado sin ningún tipo de
restricción por la clase derivada, tanto en su propio código como desde el
exterior de la clase que la ha definido.
b) Un miembro declarado como private en la clase base, aunque pertenece a la
clase derivada, no puede ser llamado ni desde el propio código de la clase
derivada ni, por supuesto, desde ningún código exterior a su clase. Dicho de otro
modo: aunque una clase derivada incluye todos los miembros de la clase base de

3/21
Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

la que deriva, no puede acceder a aquellos miembros que han sido declarados
como private en la clase base.
c) Un miembro declarado como protected, puede ser llamado desde el interior del
código de la clase derivada pero no desde el exterior de dicha clase, es decir, los
objetos de la clase derivada no pueden acceder a ellos. Un miembro protected
es en realidad un miembro private para cualquier clase excepto para una clase
derivada, que es public, con la excepción antes aludida.

El siguiente ejemplo sirve para ilustrar lo anterior:

using System;

namespace AccesoConHerencia
{
public class Base
{
private int xPri;
public int yPub;
protected int zPro;

public void Imprimir()


{
//este metodo puede llamar a todos los miembros
// public y private desde la propia clase
Console.WriteLine("xPri={0},yPub={1},zPro={2}",xPri,
yPub, zPro);
}
}
public class Derivada : Base
{
int x;
public void Suma()
{
//Este método no puede utilizar xPri,
//por ser privado en la clase base
Console.WriteLine("Suma={0}",yPub+zPro+x);
}
}
class Aplicacion
{
static void Main(string[] args)
{
Base unaClaseBase=new Base();

//ERROR. Miembro privat


//unaClaseBase.xPri=2;

unaClaseBase.yPub=3;

//ERROR. Miembro protected


//unaClaseBase.zPro=4;

Derivada unaClaseDerivada=new Derivada();

//ERROR. Miembro private


//unaClaseDerivada.xPri=12;

4/21
Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

unaClaseDerivada.yPub=13;

//ERROR. Es miembro protected


//unaClaseDerivada.zPro=14;

//ERROR. Es miembro private


//unaClaseDerivada.x=15;

unaClaseBase.Imprimir();
unaClaseDerivada.Imprimir();
unaClaseDerivada.Suma();

}
}

La salida de este programa es:


xPri=0; yPub=3, zPro=0
xPri=0; yPub=13, zPro=0
Suma=13

La herencia es muy importante en la programación orientada a objetos. C# proporciona


una serie de librerías que es muy importante conocer para poder aprovecharla en su
integridad. En la totalidad de los programas gráficos, el programador aprovecha ese
código ya optimizado y probado por Microsoft heredando de algunas clases para con
algunas modificaciones y añadidos escribir su código.

Sobrescritura

Muchas veces resulta muy útil y dota a la programación orientada a objetos de


una extraordinaria flexibilidad, poder redefinir –sobrescribir- un método o
propiedad de la clase base en la clase derivada.

En C#, el programador debe indicar qué métodos de una clase pueden ser sobrescritos y
cuáles no. Para ello lo debe indicar por medio del modificador virtual. En la clase
derivada se debe preceder el nombre del método del modificador override.

No es posible cambiar la accesibilidad del método en la clase derivada.

La palabra reservada base permite utilizar los métodos de la clase base que están
sobrescritos en la clase derivada.

Para explicar este concepto, se va a utilizar un ejemplo anterior, se va a sobrescribir el


método Imprimir():

using System;
namespace ConsoleApplication1
{

5/21
Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

public class ClaseBase


{
public int a;
public int b;
public virtual void Imprimir()
{
Console.WriteLine("a={0},b={1}",a,b);
}
}

public class ClaseDerivada:ClaseBase


{
public int c;
public override void Imprimir()
{
Console.WriteLine("a={0},b={1},c={2}",a,b,c);
}
}
class HerenciaApp
{
static void Main(string[] args)
{
ClaseBase claseBase=new ClaseBase();
ClaseDerivada claseDerivada=new ClaseDerivada();

claseBase.a=11;
claseBase.b=12;
claseBase.Imprimir();

claseDerivada.a=25;
claseDerivada.b=26;
claseDerivada.c=27;
claseDerivada.Imprimir();

}
}
}

La salida de este programa es:

a=11, b=12
a=25, b=26, c=27

En este sencillo ejemplo, se ha sobrescrito el método Imprimir(). Para ello, en la clase


base se define Imprimir() precedido del modificador virtual:

public virtual void Imprimir()

En la clase derivada, el método que se sobrescribe debe tener el mismo nombre e ir


precedido del modificador override, que indica que en la clase base existe un método
con el mismo nombre y con código diferente.

public override void Imprimir()

Dependiendo del objeto que lo llama, el compilador decide si utiliza el método de la


clase base o el de la derivada.

6/21
Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

Para invocar un método de la clase base que está sobrescrito en la clase derivada se
utiliza la referencia base.

Por ejemplo, si desde la clase base se desea llamar al método Imprimir() de la clase
base se invoca de la siguiente forma:

base.Imprimir().

Por ejemplo, se podría haber definido el método Imprimir() en la clase derivada de la


siguiente manera:

public class ClaseDerivada:ClaseBase


{
public int c;
public override void Imprimir()
{
base.Imprimir();
Console.WriteLine("c={0}",c);
}
}

El mismo código del ejemplo anterior daría una salida del programa ligeramente
diferente:
a=11, b=12
a=25, b=26
c=27

Esto es así porque al ejecutar


base.Imprimir();

el compilador realiza un salto de línea. Sin embargo, en el siguiente apartadose tratará


este concepto con más detalle.

Constructores y herencia

Orden de ejecución de los constructores

Cuando se invoca a un constructor de la clase derivada, se ejecuta previamente, por


defecto, el constructor de la clase base. Por ejemplo:

using System;

namespace Herencia
{
public class ClaseBase
{
int x,y;
public ClaseBase()
{
Console.WriteLine("Constructor de la clase base");
}

7/21
Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

}
public class ClaseDerivada : ClaseBase
{
int z;
public ClaseDerivada()
{
Console.WriteLine("Constructor de la clase derivada");
}
}
public class Aplicacion
{
static void Main(string[] args)
{
ClaseDerivada d=new ClaseDerivada();
}
}
}

La salida de este programa es la siguiente:


Constructor de la clase base
Constructor de la clase derivada

Palabra reservada base

En ocasiones, puede ser interesante ejecutar el constructor de la clase base


proporcionándole algunos argumentos o ejecutar algún método de la clase base desde el
código de la clase derivada. Para ello se utiliza la palabra base.

base es muy parecido a this. La diferencia más importante es que la segunda se refiere
siempre al propio objeto, y base hace referencia a la superclase, a la clase de la que
deriva la clase actual, a la clase padre. base puede utilizarse de dos formas:

A) Para hacer referencia a alguno de los constructores de la clase base en la clase


derivada.

Como ejemplo, se va a calcular el área de un cilindro. Se definen las clases Circulo -


con dos campos, uno constante y que define PI, y el radio- y la clase Cilindro, que se
considera como un círculo al que se le ha añadido una altura. El área de un círculo es
el producto de π por el cuadrado del radio, y el de un cilindro es el área de la base –que
es un círculo- por la altura. De esta manera, se tendrá que sobrescribir el método Area()
en la clase derivada.

using System;

namespace TemasMatematicos
{
public class Circulo
{
public const double PI=3.141592;
protected double radio;
public Circulo(double radio)
{
this.radio=radio;

8/21
Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

}
public virtual double Area()
{
return PI*radio*radio;
}
}
public class Cilindro : Circulo
{
double altura;
public Cilindro(double radio,double altura):base(radio)
{
this.altura=altura;
}
public override double Area()
{
return PI*radio*radio*altura;
}
}
class Aplicacion
{
static void Main(string[] args)
{
Cilindro c=new Cilindro(1,2);
Console.WriteLine("Area={0}",c.Area());
}
}
}

La salida de este programa es:

Area=6.283184

Este programa tiene alguna líneas en la que en importante detenerse.

a) public virtual double Area()


En la clase base, Circulo, se define el método Area() como virtual, para
indicar que será sobrescrito en la clase derivada.

b) public override double Area()

En la clase Cilindro que deriva de la clase Circulo, se sobrescribe este método


para calcular el área del cilindro ya que parece que se debería llamar de la
misma manera y se precede del modificador override.

c) public Cilindro(double radio,double altura):base(radio)


En esta sentencia se invoca en primer lugar el constructor de la clase base para
que inicialice el radio y después se ejecuta el código de la clase derivada.

En general, para ejecutar el constructor de la clase base, en la misma línea donde


se define el constructor de la clase derivada, se sigue la palabra base con los
argumentos necesarios para que se ejecute un determinado constructor de la
superclase precedida de :.

Dependiendo del constructor de la clase base, se utilizan unos argumentos u


otros.

9/21
Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

También se puede llamar a un constructor de la misma clase, escribiendo


después del constructor :this(),de la misma forma que se ha hecho con
:base().

B) Para invocar un método de la clase base desde la clase derivada –se utiliza
cuando dicho método está sobrecargado-.

Este concepto se ha explicado brevemente con anterioridad. Sin embargo, se aprovecha


este ejemplo para profundizar un poco más en él.

Si se quiere invocar un método de la clase base se puede invocar con la palabra


reservada base, de la misma forma que se utiliza para llamar al objeto actual con this.

Así, en el ejemplo anterior, si se quiere invocar el método Area() de la clase Circulo,


desde el código de la clase derivada se hace de la siguiente manera:

base.Area().

En ese mismo bloque de código –es decir, en la clase derivada- para referirse al método
Area() de la clase Cilindro, se puede hacer de dos formas:

Area()

o bien

this.Area()

Por ejemplo,

public class ClaseDerivada : ClaseBase


{
public void HacerAlgoDeAlgunaManera()
{
base.HacerAlgo();
//realizar el resto del procedimiento
}
}

En el ejemplo se observa que con la línea:

base.HacerAlgo();

se refiere al método de la clase base, que tendrá el mismo nombre que en la clase
derivada.

Polimorfismo de referencias

Se puede asignar a una referencia de una superclase una referencia de una subclase. Esta
es una característica muy útil y tremendamente potente de C#, que también se encuentra
en C++ y en Java, aunque con diferencias sustanciales.

10/21
Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

El siguiente ejemplo sirve para explicar este concepto:


using System;

namespace Polimorfismo
{
public class Caja
{
protected double x;
protected double y;
protected double z;

public Caja(double ancho, double largo, double alto)


{
x=ancho;
y=largo;
z=alto;
}
public double Volumen()
{
return x*y*z;
}
}
public class CajaConPeso : Caja
{
public double Peso;
public CajaConPeso(double a, double b, double c,
double peso) : base(a,b,c)
{
this.Peso=peso;
}
}

public class MiClaseApp


{
static void Main(string[] args)
{
//Se obtienen referencias a objetos
//de las clases base y derivada
Caja refCaja=new Caja(1,2,3);
CajaConPeso refCajaConPeso=new CajaConPeso(3,4,5,6);

double volumen;
volumen=refCaja.Volumen();
Console.WriteLine("Vol={0}",volumen);

//se asigna una referencia de la clase base


//a una refencia. de la clase derivada
refCaja=refCajaConPeso;

volumen=refCaja.Volumen();
Console.WriteLine("Vol={0}",volumen);
//Console.WriteLine("Peso={0}", refCaja.Peso);

}
}
}

La salida de este programa es:


Vol=6

11/21
Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

Vol=60

refCajaConPeso es una referencia a un objeto de la clase CajaConPeso –clase


derivada- y refCaja una referencia a un objeto de la clase Caja –clase base-. Como
CajaConPeso es una subclase de Caja es posible asignar a refCaja la referencia
refCajaConPeso.

Es muy importante entender bien que el que determina qué miembros son accesibles y
cuáles no es el tipo de variable de referencia ni el tipo de objeto al que se refiere.
Cuando una referencia a un objeto de una subclase se asigna a una referencia de la
superclase, sólo se tiene acceso a aquéllas partes del objeto definidas en la superclase
(figura 6.1).

Por eso, aunque ahora refCaja y refCajaConPeso referencian al mismo objeto de la


clase CajaConPeso, refCaja no puede acceder al campo Peso a pesar de ser una
referencia a un objeto de la clase CajaConPeso. Esto tiene sentido ya que la clase base –
la superclase- no tiene conocimiento –no puede “ver” y por lo tanto tampoco acceder o
invocar- a lo que en la clase derivada se añade a su propia definición. No es posible para
una referencia de tipo Caja acceder al campo Peso ya que no ha sido definido en la
clase base Caja. Por eso, la última línea de este programa está comentada, porque daría
un error al compilar.

Se podría haber creado esa referencia directamente, al crear un objeto de la clase


derivada, es decir,

Caja refCaja=new CajaConPeso(3,4,5,6);

En este caso, refCaja es una referencia de tipo Caja a un objeto de tipo CajaConPeso.

Esto es extraordinariamente importante y dota de una gran flexibilidad y potencia a este


lenguaje, ya que si una superclase contiene un método que está sobrescrito en la
subclase, entonces, cuando se referencian distintos tipos de objetos a través de una
variable de referencia de la superclase, se ejecutan distintas versiones del método.

refCaja

x x
y y
refCajaCoPeso
z z
Volumen() Volumen()

Peso
Objeto tipo Caja
Objeto tipo CajaConPeso

Figura 6.1: La referencia refCaja sólo puede invocar los miembros de la clase
derivada que conoce, que son los que tiene en la superclase.

12/21
Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

por ejemplo: suponga que se desea definir un método que puede admitir como
parámetro cualquier tipo de dato. Dicho método podría tener la siguiente forma:

public void UnMetodo(object obj)


{
//codigo
}

Como hasta ahora se ha venido repitiendo, todo dato en C# es un objeto, deriva de él y


por lo tanto puede tratarse como tal. El polimorfismo de referencias nos permite escribir
el siguiente fragmento de código:

string unString = “Hola Mundo”;


int unEntero = 3;
const double PI = 3.14;
MiClase unaClase = new MiClase();
ClaseDeMicrosoft otraClase = new ClaseDeMicrosoft();
UnMetodo(unString);
UnMetodo(unEntero);
UnMetodo(PI);
UnMetodo(unaClase);
UnMetodo(otraClase);

Observe que no se ha sobrecargado el método UnMetodo. El código anterior es posible


por el polimorfismo de referencias, porque realmente, ocurre lo siguiente:
object refObj; //Inicialmente null, no referencia a nada
string unString = “Hola Mundo”;
refObj = unString;
UnMetodo(refObj);
....

La referencia al string unString, apunta a un objeto que es la cadena “Hola Mundo”,


que como todo string es un objeto, es decir, deriva de object. Por el polimorfismo de
referencias, es posible asignar una referencia de una clase –en este caso un string- a
una referencia de una superclase –en este caso a object-. Esta técnica permite tratar
los datos de manera genérica.

El polimorfismo de referencias existe también en Java pero la técnica para conseguirlo


es menos directa y complicada que en C#, ya que en Java no todos los tipos de datos son
objetos. En concreto, no lo son los llamados “tipos simples”, aunque Java proporciona a
todos ellos una clase intermedia –llamada “envoltorio” que los convierte en clases. Pero
siempre es necesario realizar un paso intermedio para convertir los llamados tipos
básicos a tipos de su clase “envoltorio”. En C# no es necesario realizar este paso,
porque los tipos de datos son objetos. Esta manera de trabajar proporciona una enorme
potencia a este lenguaje.

13/21
Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

Interfaces.

Una interface garantiza un determinado comportamiento de una clase o estructura.


Cuando una clase implementa una interface se garantiza que soportará los métodos,
propiedades, eventos e indexadores de la interface. La interface es una alternativa a
una clase abstracta.

Una interface es similar a una clase o a una estructura, pero sus miembros son
abstractos, es decir, no están definidos, no tienen código. Declara modos de
comportamiento, pero deja su definición para las clases que la implementen.

Cuando una clase implementa una interface, debe implementar todos los métodos de
la interface.

Hay una gran diferencia entre heredar de una clase abstracta e implementar una
interface. Por ejemplo: un Coche es un vehículo –hereda las características y
comportamiento de un vehículo-, pero puede tener la capacidad de
PoderRegularLaTemperatura (como una casa, por ejemplo). Cuando se hereda, se
hace referencia a lo que se es, y cuando se implementa una interface se hace
referencia a la capacidad de “comportarse” de una determinada manera.

En este capítulo se estudia cómo crear, implementar y usar las interfaces. Además, se
tratará cómo implementar múltiples interfaces.

Estructura de una interface.


La estructura de una interface es la siguiente:

[atributosOPC] [modificadores de accesoOPC] interface NombreDeLaInterface [:interfaces-


base]
{
//Cuerpo de la interface
}

Los atributos se tratarán más adelante. Por ahora es suficiente con decir que los
atributos en C# contienen información sobre el tipo de dato y puede ser consultada
mediante reflexión. Los atributos son opcionales.

Los modificadores de acceso –opcionales- pueden ser los mismos que los de las clases y
con el mismo efecto, a excepción de abstract y sealed, que no tienen sentido para
una interface, es decir: new, public, protected, internal y private.

La palabra interface es seguida del nombre de la interface. Generalmente –aunque


no hay porqué hacerlo así- se suele utilizar -como convenio- un nombre que comience
por la letra mayúscula I, como IClonable, IMiInterface, etc...

Si la interface hereda a su vez de otras interfaces, entonces, después del nombre de la


interface se escribe dos puntos (:) y, separados por comas, los nombres de las
interfaces que implementa.

14/21
Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

Por ejemplo:

interface IAvion : IVolar , IVehiculo


{
//Código
}

Suponga que desea crear una interface que defina la capacidad de “ser imprimible” que
se llame IImprimible y que tenga un método que se llame Imprimir()

La definición sería:

interface IImprimible
{
void Imprimir();
}

Por ejemplo, se puede crear una clase Documento. Para indicar que el tipo Documento se
“puede imprimir” basta con que implemente la interface IImprimible. Implementar
una interfce no es más que escribir código en los métodos definidos en la interface.
La sintaxis es la misma que si derivara de la interface, aunque, como se ha dicho, es
necesario implementar todos los métodos de la interface. Por ejemplo:

public interface IImprimible


{
void Imprimir();
}

public class Documento : IImprimible


{
string contenido;
public Documento(string frase)
{
contenido=frase;
}
public void Imprimir()
{
Console.WriteLine(contenido);
}
}

class MiAplicacion
{
static void Main(string[] args)
{
Documento unDocumento=new Documento("Contenido 1");
unDocumento.Imprimir();
}
}

La salida de este programa es:

Contenido 1

15/21
Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

Suponga que se desea crear otra interface que defina el comportamiento necesario
para guardar y leer desde una fuente de datos –una base de datos, o el disco duro, ...-. Se
le llamará, por ejemplo, IArchivable. Esta interface podría tener dos métodos:
Leer() y Escribir().

Una posible definición es, por ejemplo:

interface IArchivable
{
void Leer();
void Escribir()
}

Para indicar que la clase Documento tiene la capacidad de ser almacenado en el disco
duro, basta con que implemente la interface IArchivable e IImprimible.
public class Documento: IImprimible, IArchivable
{
void Leer()
{
//Código que implementa el método
}
void Escribir()
{
//Código que implementa el método
}
public void Imprimir()
{
//Código que implementa el método
}
//Otros miembros y código propio de la clase Documento
}

Es responsabilidad del autor de la clase Documento implementar o definir el


comportamiento de los métodos de la interface. Si una clase implementa una
interface debe implementar de manera obligatoria todos los métodos de la
interface.

Todos los miembros de una interface son públicos –por defecto- para que puedan ser
implementados por otras clase. Por esta razón no llevan ningún modificador.

A continuación se escribe un ejemplo completo. Se define una nueva clase,


Rectángulo, que implemente la interface IImprimible. Se puede observar cómo se
definen de manera diferente el mismo método Imprimir() en las dos clases y cómo el
Rectángulo no tiene la capacidad de ser almacenado en disco.

El ejemplo completo sería:

interface IArchivable
{
void Leer();
void Escribir();
}

16/21
Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

public interface IImprimible


{
void Imprimir();
}

public class Rectangulo : IImprimible


{
int ancho;
int alto;
public Rectangulo(int lado1, int lado2)
{
ancho=lado1;
alto=lado2;
}
public void Imprimir()
{
Console.WriteLine("ancho={0},alto={1}",ancho,alto);
}
}

public class Documento : IImprimible, IArchivable


{
string contenido;
public Documento(string frase)
{
contenido=frase;
}
public void Imprimir()
{
Console.WriteLine(contenido);
}
public void Leer()
{
Console.WriteLine("Leyendo CONTENIDO desde le disco duro");
}
public void Escribir()
{
Console.WriteLine("Escribiendo CONTENIDO en disco duro");
}
}
class MiAplicacion
{
static void Main(string[] args)
{
Documento unDocumento=new Documento("Contenido 1");
Rectángulo unRect = new Rectángulo(12,13);
unDocumento.Escribir();
unDocumento.Leer();
unDocumento.Imprimir();
unRect.Escribir();

}
}

Polimorfismo de referencias

Se vuelve a estudiar aquí el polimorfismo de referencias, pero ahora desde un punto de


vista distinto: la interface.

17/21
Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

No es posible crear objetos de tipo interface. Esto es lógico si pensamos que sus
métodos están declarados, pero no definidos. No tendría sentido un objeto sin métodos
implementados.

Por esta razón, no se puede escribir:

IImprimible refInterface = new IImprimible();

Sin embargo, puede crearse una referencia de tipo interface para que apunte a
cualquier objeto de un tipo que implemente dicha interface. Por ejemplo:

IImprimible refInterface;
Documento unDocumento=new Documento(“texto del documento”);
refInterface = (IImprimible)unDocumento;

o bien, de manera más comprimida:

IImprimible refInterface=(IImprimible)new Documento(“otro texto ”);

Se tiene la garantía de que cualquier clase que implemente la interface IImprimible


tendrá implementado el método Imprimir(). Por eso, tiene sentido que una referencia a
una interface pueda invocar todos los métodos -que tenga declarados como
interface- del objeto al que apunta. Sin embargo, la interface IImprimible “no
conoce” cómo cada clase lo implementa –ni le hace falta saberlo- ni qué más miembros
tienen dichas clases. Por eso no puede invocar aquellos métodos, propiedades, etc, que
sean únicamente propios del objeto.

Para ilustrar esta idea, piense en la aplicación anterior con el siguiente código en el
método Main():

Documento doc=new Documento("contenido del documento");


IImprimible refInterfaceImprimible=doc;
refInterfaceImprimible.Imprimir();

o bien:
IImprimible refInterfaceImprimible;
refInterfaceImprimible=new Documento("Una frase");
refInterfaceImprimible.Imprimir();

En resumen: una referencia a una determinada interface, puede apuntar a cualquier


objeto de una clase o estructura que la implemente. Esto convierte el polimorfismo de
referencias en una herramienta extremadamente flexible y poderosa.

En el ejemplo anterior la clase Rectangulo implementa la interface IImprimible y


la clase Documento las interfaces IImprimible e IArchivable. Gracias al
polimorfismo de referrencias se puede escribir:
Documento doc=new Documento("contenido del documento");
Rectangulo rect=new Rectangulo(12,10);
IImprimible refInterfaceImprimible=doc;

18/21
Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

refInterfaceImprimible.Imprimir();
refInterfaceImprimible=rect;
refInterfaceImprimible.Imprimir();

o bien:
IImprimible refInterfaceImprimible;
refInterfaceImprimible=new Documento("Una frase");
refInterfaceImprimible.Imprimir();
refInterfaceImprimible=new Rectangulo(12,10);
refInterfaceImprimible.Imprimir();

Miembros de interface.
Los miembros de una interface son:

- Los que hereda de las interfaces base.


- Los que se declaran en la propia interface.

Una interface puede declarar cero o más miembros, los cuales tienen acceso public
por defecto. Por lo tanto, los miembros de una interface no pueden ser declarados con
los modificadores abstract, public, protected, internal, private, virtual,
override, o static.

Herencia de interfaces.
Una interface puede heredar cero o más interfaces de modo explícito (herencia
múltiple de interfaces). A tales interfaces se les llama “interfaces base explícitas”. No
obstante, una interface no sólo hereda las interface que explícitamente indica, sino
también aquéllas heredadas implícitamente, es decir, aquéllas que han heredado las
interface de las que hereda.

Por ejemplo:

interface IControl
{
void Paint();
}
// ITextBox hereda de IControl
interface ITextBox: IControl
{
void SetText(string text);
}

// IListBox hereda de IControl

interface IListBox: IControl


{
void SetItems(string[] items);
}
interface IComboBox: ITextBox, IListBox
{
//....
}

19/21
Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

La interface IComboBox tendrá todos los métodos de las interfaces ITextBox y de


IListBox, y por lo tanto, los de IControl, ya que implícitamente la hereda.

Implementación de una interface.


Las interfaces pueden ser implementadas mediante clases y estructuras. Para indicar que
una clase o estructura implementa una interface se ha de incluir el identificador de la
interface en la lista de tipos base de la clase o estructura.

Ejemplo:

interface IControl
{
void Paint();
}
interface ITextBox: IControl
{
void SetText(string text);
}
class TextBox: ITextBox
{
public void Paint() {...}
public void SetText(string texto) {…}
}

En este caso la clase TextBox no sólo implementa ITextBox, sino también IControl.

Utilización de los operadores is y as con interfaces


Este operador se utiliza para averiguar si un determinado objeto soporta una interface.

Su formato general es:

objeto is tipo

La expresión anterior devuelve True en caso de que objeto soporte o implementa la


interface tipo. En caso contrario devuelve False.

Por ejemplo:

Rectangulo rect = new Rectangulo(13,15);


if( rect is IImprimible )
{
IImprimible refInterface = (IImprimible) rect;
refInterface.Imprimir();
}
....

Pero el uso de is es ineficaz porque puede generar excepciones. Una solución mejor es
utilizar el operador as.

20/21
Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

Este operador combina el operador is y un casting o conversión de tipos. Primero se


chequea si la conversión es válida y si es así, se realiza la conversión. En caso contrario,
se devuelve null. La expresión general es:

objeto as tipo

Por ejemplo:

Rectangulo rect = new Rectangulo(13,15);


IImprimible refInterface = rect as IImprimible;
if(refInterface != null)
refInterface.Imprimir();
else
Console.WriteLine(“Rectangulo no soporta Iimprimible”)
....

21/21

También podría gustarte