21
Marco Besteiro y Miguel Rodríguez Herencia e Interfaces 1/21 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

Herencia e Interfaces - UPV/EHUehu.eus/mrodriguez/archivos/csharppdf/Lenguaje/Herencia_Interfaces.pdf · Marco Besteiro y Miguel Rodríguez Herencia e Interfaces 3/21 En este ejemplo

Embed Size (px)

Citation preview

Page 1: Herencia e Interfaces - UPV/EHUehu.eus/mrodriguez/archivos/csharppdf/Lenguaje/Herencia_Interfaces.pdf · Marco Besteiro y Miguel Rodríguez Herencia e Interfaces 3/21 En este ejemplo

Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

1/21

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

Page 2: Herencia e Interfaces - UPV/EHUehu.eus/mrodriguez/archivos/csharppdf/Lenguaje/Herencia_Interfaces.pdf · Marco Besteiro y Miguel Rodríguez Herencia e Interfaces 3/21 En este ejemplo

Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

2/21

{ 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

Page 3: Herencia e Interfaces - UPV/EHUehu.eus/mrodriguez/archivos/csharppdf/Lenguaje/Herencia_Interfaces.pdf · Marco Besteiro y Miguel Rodríguez Herencia e Interfaces 3/21 En este ejemplo

Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

3/21

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

Page 4: Herencia e Interfaces - UPV/EHUehu.eus/mrodriguez/archivos/csharppdf/Lenguaje/Herencia_Interfaces.pdf · Marco Besteiro y Miguel Rodríguez Herencia e Interfaces 3/21 En este ejemplo

Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

4/21

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;

Page 5: Herencia e Interfaces - UPV/EHUehu.eus/mrodriguez/archivos/csharppdf/Lenguaje/Herencia_Interfaces.pdf · Marco Besteiro y Miguel Rodríguez Herencia e Interfaces 3/21 En este ejemplo

Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

5/21

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 {

Page 6: Herencia e Interfaces - UPV/EHUehu.eus/mrodriguez/archivos/csharppdf/Lenguaje/Herencia_Interfaces.pdf · Marco Besteiro y Miguel Rodríguez Herencia e Interfaces 3/21 En este ejemplo

Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

6/21

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.

Page 7: Herencia e Interfaces - UPV/EHUehu.eus/mrodriguez/archivos/csharppdf/Lenguaje/Herencia_Interfaces.pdf · Marco Besteiro y Miguel Rodríguez Herencia e Interfaces 3/21 En este ejemplo

Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

7/21

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"); }

Page 8: Herencia e Interfaces - UPV/EHUehu.eus/mrodriguez/archivos/csharppdf/Lenguaje/Herencia_Interfaces.pdf · Marco Besteiro y Miguel Rodríguez Herencia e Interfaces 3/21 En este ejemplo

Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

8/21

} 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;

Page 9: Herencia e Interfaces - UPV/EHUehu.eus/mrodriguez/archivos/csharppdf/Lenguaje/Herencia_Interfaces.pdf · Marco Besteiro y Miguel Rodríguez Herencia e Interfaces 3/21 En este ejemplo

Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

9/21

} 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.

Page 10: Herencia e Interfaces - UPV/EHUehu.eus/mrodriguez/archivos/csharppdf/Lenguaje/Herencia_Interfaces.pdf · Marco Besteiro y Miguel Rodríguez Herencia e Interfaces 3/21 En este ejemplo

Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

10/21

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.

Page 11: Herencia e Interfaces - UPV/EHUehu.eus/mrodriguez/archivos/csharppdf/Lenguaje/Herencia_Interfaces.pdf · Marco Besteiro y Miguel Rodríguez Herencia e Interfaces 3/21 En este ejemplo

Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

11/21

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

Page 12: Herencia e Interfaces - UPV/EHUehu.eus/mrodriguez/archivos/csharppdf/Lenguaje/Herencia_Interfaces.pdf · Marco Besteiro y Miguel Rodríguez Herencia e Interfaces 3/21 En este ejemplo

Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

12/21

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. 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.

Objeto tipo CajaConPeso Objeto tipo Caja

refCaja

refCajaCoPeso

Peso

x y z Volumen()

x y z Volumen()

Page 13: Herencia e Interfaces - UPV/EHUehu.eus/mrodriguez/archivos/csharppdf/Lenguaje/Herencia_Interfaces.pdf · Marco Besteiro y Miguel Rodríguez Herencia e Interfaces 3/21 En este ejemplo

Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

13/21

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.

Page 14: Herencia e Interfaces - UPV/EHUehu.eus/mrodriguez/archivos/csharppdf/Lenguaje/Herencia_Interfaces.pdf · Marco Besteiro y Miguel Rodríguez Herencia e Interfaces 3/21 En este ejemplo

Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

14/21

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.

Page 15: Herencia e Interfaces - UPV/EHUehu.eus/mrodriguez/archivos/csharppdf/Lenguaje/Herencia_Interfaces.pdf · Marco Besteiro y Miguel Rodríguez Herencia e Interfaces 3/21 En este ejemplo

Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

15/21

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

Page 16: Herencia e Interfaces - UPV/EHUehu.eus/mrodriguez/archivos/csharppdf/Lenguaje/Herencia_Interfaces.pdf · Marco Besteiro y Miguel Rodríguez Herencia e Interfaces 3/21 En este ejemplo

Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

16/21

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(); }

Page 17: Herencia e Interfaces - UPV/EHUehu.eus/mrodriguez/archivos/csharppdf/Lenguaje/Herencia_Interfaces.pdf · Marco Besteiro y Miguel Rodríguez Herencia e Interfaces 3/21 En este ejemplo

Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

17/21

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.

Page 18: Herencia e Interfaces - UPV/EHUehu.eus/mrodriguez/archivos/csharppdf/Lenguaje/Herencia_Interfaces.pdf · Marco Besteiro y Miguel Rodríguez Herencia e Interfaces 3/21 En este ejemplo

Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

18/21

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;

Page 19: Herencia e Interfaces - UPV/EHUehu.eus/mrodriguez/archivos/csharppdf/Lenguaje/Herencia_Interfaces.pdf · Marco Besteiro y Miguel Rodríguez Herencia e Interfaces 3/21 En este ejemplo

Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

19/21

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 { //.... }

Page 20: Herencia e Interfaces - UPV/EHUehu.eus/mrodriguez/archivos/csharppdf/Lenguaje/Herencia_Interfaces.pdf · Marco Besteiro y Miguel Rodríguez Herencia e Interfaces 3/21 En este ejemplo

Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

20/21

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.

Page 21: Herencia e Interfaces - UPV/EHUehu.eus/mrodriguez/archivos/csharppdf/Lenguaje/Herencia_Interfaces.pdf · Marco Besteiro y Miguel Rodríguez Herencia e Interfaces 3/21 En este ejemplo

Marco Besteiro y Miguel Rodríguez Herencia e Interfaces

21/21

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”) ....