You are on page 1of 11

Herencia

Concepto de herencia Clases abstractas Clases selladas Tipos anidados Operadores especiales

Concepto de herencia
El mecanismo de herencia es uno de los pilares fundamentales en los que se basa la programacin orientada a objetos. Es un mecanismo que permite definir nuevas clases a partir de otras ya definidas. Si en la definicin de una clase indicamos que sta deriva de otra, entonces la primera -a la que se le suele llamar clase hija o clase derivada- ser tratada por el compilador automticamente como si su definicin incluyese la definicin de la segunda -a la que se le suele llamar clase padre o clase base.

Las clases que derivan de otras se definen usando la siguiente sintaxis:

class <claseHija> : <clasePadre> { <miembrosHija> } A los miembros definidos en la clase hija se le aadirn los que hubisemos definido en la clase padre: la clase derivada "hereda" de la clase base. La palabra clave base se utiliza para obtener acceso a los miembros de la clase base desde una clase derivada. C# slo permite herencia simple.

Herencia de constructores
Los objetos de una clase derivada contarn con los mismos miembros que los objetos de la clase base y adems incorporarn nuevos campos y/o mtodos. El constructor de una clase derivada puede emplear el constructor de la clase base para inicializar los campos heredados de la clase padre con la construccin base. En

realidad se trata de una llamada al constructor de la clase base con los parmetros adecuados.

: base(<parametrosBase>)

Si no se incluye el compilador considerara que vale :base(), lo que provocara un error si la clase base carece de constructor sin parmetros.

Ejemplo de "herencia" de constructores public class B { private int h; // Campo public B () { // Constructor sin parmetros this.h = -1; } public B (int h) // Constructor con parmetro { this.h = h; } public int H // Propiedad { get { return h; } set { h = value; } } } // class B public class D : B // "D" hereda de "B" { private int i; // Campo public D () : this(-1) {} // Constructor sin parmetros public D (int i) { // Constructor con un parmetro this.i = i; } public D (int h, int i) : base(h) { // Constructor con this.i = i; // dos parmetros } public int I // Propiedad { get { return i; } set { i = value; } } } // class D ...... B varB1 = new B(); // Const. sin parmetros de B B varB2 = new B(5); // Const. con 1 parmetro de B Console.WriteLine("varB1 : (H={0})", varB1.H); Console.WriteLine("varB2 : (H={0})\n", varB2.H); D varD1 = new D(); // Const. sin parmetros de D D varD2 = new D(15); // Const. con 1 parmetro de D D varD3 = new D(25, 11); // Const. con 2 parmetros de D Console.WriteLine("varD1 : (I={0},H={1})", varD1.I, varD1.H); Console.WriteLine("varD2 : (I={0},H={1})", varD2.I,

varD2.H); Console.WriteLine("varD3 : (I={0},H={1})", varD3.I, varD3.H); Console.ReadLine(); ......

En el siguiente ejemplo se muestra cmo puede extenderse la clase CocheSimple vista anteriormente para construir, a partir de ella, la clase Taxi. Observar como se emplea la construccin base para referenciar a un constructor de la clase base y que cuando acta el constructor sin parmetros de la clase Taxi se llama implcitamente al constructor sin parmetros de la clase CocheSimple. Ejemplo: herencia sobre la clase CocheSimple

using System; namespace DemoHerencia { class CocheSimple { private int VelocMax; private string Marca; private string Modelo; public CocheSimple () { this.VelocMax = 0; this.Marca = "??"; this.Modelo = "??"; } public CocheSimple (string marca, string mod, int velMax) { this.VelocMax = velMax; this.Marca = marca; this.Modelo = mod; } public void MuestraCoche () { Console.WriteLine (this.Marca + " " + this.Modelo + " (" + this.VelocMax + " Km/h)"); }

} // class CocheSimple class Taxi : CocheSimple private string CodLicencia; public Taxi () {} public Taxi (string marca, string mod, int vel, string lic) : base (marca, mod, {

vel)

this.CodLicencia = lic; } public string Licencia { get { return this.CodLicencia; } } } // class Taxi class DemoHerenciaApp { static void Main(string[] args) { CocheSimple MiCoche = new CocheSimple ("Citren", "Xsara Picasso", 220); CocheSimple TuCoche = new CocheSimple ("Opel", "Corsa", 190); CocheSimple UnCoche = new CocheSimple (); Console.Write ("Mi coche: "); MiCoche.MuestraCoche(); Console.Write ("El tuyo: "); TuCoche.MuestraCoche(); Console.Write ("Un coche sin identificar: "); UnCoche.MuestraCoche(); Console.WriteLine(); Taxi ElTaxiDesconocido = new Taxi (); Console.Write ("Un taxi sin identificar: "); ElTaxiDesconocido.MuestraCoche(); Taxi NuevoTaxi= new Taxi ("Ford", "KA", 150, "GR1234"); Console.Write ("Un taxi nuevo: "); NuevoTaxi.MuestraCoche(); Console.Write (" Licencia: {0}", NuevoTaxi.Licencia); Console.ReadLine (); } // Main } // class DemoHerenciaApp } // namespace DemoHerencia

Redefinicin de mtodos
Siempre que se redefine un mtodo que aparece en la clase base, hay que utilizar explcitamente la palabra reservada override y, de esta forma, se evitan redefiniciones accidentales (una fuente de errores en lenguajes como Java o C++). Sabemos que todos los objetos (incluidas las variables de los tipos predefinidos) derivan, en ltima instancia, de la clase Object. Esta clase proporciona el mtodo ToString que crea una cadena de texto legible para el usuario que describe una instancia de la clase. Si dejamos sin redefinir este mtodo y empleando la clase CocheSimple las siguientes instrucciones:

CocheSimple MiCoche = new CocheSimple ("Citren", "Xsara Picasso", 220); Console.WriteLine ("Mi coche: " + MiCoche.ToString());

producen el siguiente resultado:

Mi coche: DemoHerencia.CocheSimple

lo que nos invita a redefinir el mtodo ToString en la clase CocheSimple :

class CocheSimple { ... public override string ToString() { return (this.Marca + " " + this.Modelo + " (" + this.VelocMax + " Km/h)"); } ... }

Las dos instrucciones siguientes son equivalentes:

Console.WriteLine ("Mi coche: " + MiCoche.ToString()); Console.WriteLine ("Mi coche: " + MiCoche); por lo que podemos sutituir las instrucciones que muestran los datos de los objetos CocheSimple por:

Console.WriteLine ("Mi coche: " + MiCoche); Console.WriteLine ("El tuyo: " + TuCoche); Console.WriteLine ("Un coche sin identificar: " + UnCoche); y eliminamos el (innecesario) mtodo MuestraCoche, el resultado de la ejecucin del programa anterior es:

La palabra reservada base sirve para hacer referencia a los miembros de la clase base que quedan ocultos por otros miembros de la clase actual. Por ejemplo, podramos redefinir tambin el mtodo ToString de la clase Taxi empleando el mtodo redefinido ToString de la clase base CocheSencillo:

class CocheSimple { ... public override string ToString() { return (this.Marca + " " + this.Modelo + " (" + this.VelocMax + " Km/h)"); } ... } class Taxi : CocheSimple { ... public override string ToString() { return (base.ToString() + "\n Licencia: " + this.Licencia); } ... }

...... Taxi ElTaxiDesconocido = new Taxi (); Console.WriteLine ("Un taxi sin identificar: " + ElTaxiDesconocido); Taxi NuevoTaxi= new Taxi ("Citren", "C5", 250, "GR1234"); Console.WriteLine ("Un taxi nuevo: " + NuevoTaxi); ...... y el resultado es:

En la seccin dedicada a la sobrecarga de operadores introdujimos la clase Point. No haba ningn mtodo que mostrara los datos de inters de un objeto de tipo Point. Podemos sobreescribir el mtodo ToString de manera que fuera:

public class Point { ... public override string ToString() { return ("["+this.X+", "+this.Y+"]"); } ... }

Ahora las instrucciones de escritura se convierten en llamadas a este mtodo, por ejemplo:

Console.WriteLine ("p1 es: " + p1); // Console.WriteLine ("p1 es: " + p1.ToString()

El resultado de la ejecucin de ese programa ser:

Mtodos virtuales
Un mtodo es virtual si puede redefinirse en una clase derivada. Los mtodos son no virtuales por defecto. Los mtodos no virtuales no son polimrficos (no pueden reemplazarse) ni pueden ser abstractos. Los mtodos virtuales se definen en una clase base (empleando la palabra reservada virtual) y pueden ser reemplazados (empleando la palabra reservada override) en las subclases (stas proporcionan su propia -especficaimplementacin). Generalmente, contendrn una implementacin por defecto del mtodo (si no, se deberan utilizar mtodos abstractos ).

class Shape // Clase base { // "Draw" es un mtodo virtual public virtual void Draw() { ... } } class Box : Shape { // Reemplaza al mtodo Draw de la clase base public override void Draw() { ... } } class Sphere : Shape { // Reemplaza al mtodo Draw de la clase base public override void Draw() { ... } } void HandleShape(Shape s) { ... s.Draw(); // Polimorfismo ... }

HandleShape(new Box()); HandleShape(new Sphere()); HandleShape(new Shape()); NOTA: Propiedades, indexadores y eventos tambin pueden ser virtuales.

Clases abstractas
Una clase abstracta es una clase que no puede ser instanciada. Se declara empelando la palabra reservada abstract. Permiten incluir mtodos abstractos y mtodos no abstractos cuya implementacin hace que sirvan de clases base (herencia de implementacin). Como es lgico, no pueden estar "selladas".

Mtodos abstractos
Un mtodo abstracto es un mtodo sin implementacin que debe pertenecer a una clase abstracta. Lgicamente se trata de un mtodo virtual forzoso y su implementacin se realizar en una clase derivada.

abstract class Shape // Clase base abstracta { public abstract void Draw(); // Mtodo abstracto } class Box : Shape { public override void Draw() { ... } } class Sphere : Shape { public override void Draw() { ... } } void HandleShape(Shape s) { ... s.Draw(); ... } HandleShape(new Box()); HandleShape(new Sphere()); HandleShape(new Shape());

// Error !!!

Clases selladas

Una clase sellada (sealed), es una clase de la que no pueden derivarse otras clases (esto es, no puede utilizarse como clase base). Obviamente, no puede ser una clase abstracta. Los struct en C# son implcitamente clases selladas. Para qu sirve sellar clases? Para evitar que se puedan crear subclases y optimizar el cdigo (ya que las llamadas a las funciones de una clase sellada pueden resolverse en tiempo de compilacin).

Tipos anidados
C# permite declarar tipos anidados, esto es, tipos definidos en el mbito de otro tipo. El anidamiento nos permite que el tipo anidado pueda acceder a todos los miembros del tipo que lo engloba (independientemente de los modificadores de acceso) y que el tipo est oculto de cara al exterior (salvo que queramos que sea visible, en cuyo caso habr que especificar el nombre del tipo que lo engloba para poder acceder a l).

Operadores especiales
is
Se utiliza para comprobar dinmicamente si el tipo de un objeto es compatible con un tipo especificado (instanceof en Java).

No conviene abusar de este operador (es preferible disear correctamente una jerarqua de tipos).

static void DoSomething(object o) { if (o is Car) ((Car)o).Drive(); }

as
Intenta convertir de tipo una variable (al estilo de los casts dinmicos de C++). Si la conversin de tipo no es posible, el resultado es null. Es ms eficiente que el operador is, si bien tampoco es conveniente abusar del operador as.

static void DoSomething(object o) { Car c = o as Car; if (c != null) c.Drive(); }

typeof
El operador typeof devuelve el objeto derivado de System.Type correspondiente al tipo especificado. De esta forma se puede hacer reflexin para obtener

dinmicamente informacin sobre los tipos (como en Java).

... Console.WriteLine(typeof(int).FullName); Console.WriteLine(typeof(System.Int).Name); Console.WriteLine(typeof(float).Module); Console.WriteLine(typeof(double).IsPublic); Console.WriteLine(typeof(Point).MemberType); ...

You might also like