Herencia y clases derivadas (C# y Java)

Actualización: noviembre 2007

La funcionalidad de una clase existente se puede extender al crear una nueva clase que se deriva de ella. La clase derivada hereda las propiedades de la clase base y es posible agregar o reemplazar métodos y propiedades según sea necesario.

En C#, el operador :, que equivale a extends e implements en Java, define la herencia e implementación de interfaces. La clase base siempre debe estar en el extremo izquierdo en la declaración de clase.

Como Java, C# no admite herencia múltiple, lo que significa que las clases no pueden heredar más de una clase. Sin embargo, se pueden utilizar interfaces para ese propósito, de la misma manera que en Java.

El código siguiente define una clase denominada CoOrds con dos variables miembro privadas x e y que representan la posición del punto. Se tiene acceso a estas variables mediante propiedades denominadas X e Y, respectivamente:

public class CoOrds
{
    private int x, y;

    public CoOrds()  // constructor
    {
        x = 0;
        y = 0;
    }

    public int X
    {
        get { return x; }
        set { x = value; }
    }

    public int Y
    {
        get { return y; }
        set { y = value; }
    }
}

Una nueva clase, denominada ColorCoOrds, se deriva de la clase CoOrds del siguiente modo:

public class ColorCoOrds : CoOrds

Luego, ColorCoOrds hereda todos los campos y métodos de la clase base, a la cual se pueden agregar nuevos campos y métodos para proporcionar características adicionales en la clase derivada, según sea necesario. En este ejemplo, se agrega un miembro privado y descriptores de acceso para agregar color a la clase:

public class ColorCoOrds : CoOrds
{
    private System.Drawing.Color screenColor;


    public ColorCoOrds()  // constructor
    {
        screenColor = System.Drawing.Color.Red;
    }

    public System.Drawing.Color ScreenColor
    {
        get { return screenColor; }
        set { screenColor = value; }
    }
}

El constructor de la clase derivada llama implícitamente al constructor de la clase base o la superclase, en terminología de Java. En caso de herencia, se llama a todos los constructores de clase base antes que a los constructores de la clase derivada en el orden en que las clases aparecen en la jerarquía de clases.

Convertir un tipo a una clase base

Como en Java, no se puede utilizar una referencia a una clase base para tener acceso a los miembros y métodos de una clase derivada, aunque la referencia de la clase base pueda contener una referencia válida a un objeto del tipo derivado.

Implícitamente, se puede hacer referencia a una clase derivada con una referencia al tipo derivado:

ColorCoOrds color1 = new ColorCoOrds();
CoOrds coords1 = color1;

En este código, la referencia de clase base, coords1, contiene una copia de la referencia color1.

La palabra clave base

Se puede tener acceso a los miembros de clase base en una subclase incluso cuando los miembros de base se reemplazan en la superclase utilizando la palabra clave base. Por ejemplo, puede crear una clase derivada que contenga un método con la misma firma que la clase base. Si se precede ese método con la palabra clave new, se indica que se trata de un método totalmente nuevo que pertenece a la clase derivada. También se podría proporcionar un método para tener acceso al método original de la clase base con la palabra clave base.

Por ejemplo, supongamos que la clase base CoOrds tuviera un método denominado Invert() que intercambia las coordenadas x e y. Se podría proporcionar un sustituto para este método en la clase derivada ColorCoOrds con un código como éste:

public new void Invert()
{
    int temp = X;
    X = Y;
    Y = temp;
    screenColor = System.Drawing.Color.Gray;
}

Como se puede observar, este método intercambia x e y, luego establece el color del punto en gris. Se podría proporcionar acceso a la implementación base para este método creando otro método en ColorCoOrds, como el de este ejemplo:

public void BaseInvert()
{
    base.Invert();
}

A continuación, se invoca el método base en un objeto ColorCoOrds mediante una llamada al método BaseInvert().

ColorCoOrds color1 = new ColorCoOrds();
color1.BaseInvert();

Recuerde que se obtendría el mismo efecto si se asignara una referencia de la clase base a una instancia de ColorCoOrds y, a continuación, se tuviera acceso a sus métodos:

CoOrds coords1 = color1;
coords1.Invert();

Seleccionar los constructores

Los objetos de clase base siempre se construyen antes que cualquier clase derivada. De esta forma, el constructor de la clase base se ejecuta antes que el constructor de la clase derivada. Si la clase base tiene más de un constructor, la clase derivada puede decidir a qué constructor se va a llamar. Por ejemplo, podría modificar la clase CoOrds para agregar un segundo constructor, del siguiente modo:

public class CoOrds
{
    private int x, y;

    public CoOrds()
    {
        x = 0;
        y = 0;
    }

    public CoOrds(int x, int y)
    {
        this.x = x;
        this.y = y;
    }
}

Luego, podría cambiar la clase ColorCoOrds para utilizar uno de los constructores disponibles mediante la palabra clave base:

public class ColorCoOrds : CoOrds
{
    public System.Drawing.Color color;

    public ColorCoOrds() : base ()
    {
        color = System.Drawing.Color.Red;
    }

    public ColorCoOrds(int x, int y) : base (x, y)
    {
        color = System.Drawing.Color.Red;
    }
}

En Java, esta funcionalidad se implementa con la palabra clave super.

Reemplazar el método

Una clase derivada puede reemplazar el método de una clase base si se proporciona una nueva implementación del método declarado. Una diferencia importante entre Java y C# es que, de forma predeterminada, los métodos de Java se marcan como virtuales, mientras que en C# los métodos se deben marcar explícitamente como virtuales con el modificador virtual. Los descriptores de acceso de propiedades, así como los métodos, se pueden reemplazar de manera muy similar.

Métodos virtuales

Un método que será reemplazado en una clase derivada se declara con el modificador virtual. En una clase derivada, el método reemplazado se declara con el modificador override.

El modificador override denota un método o propiedad de una clase derivada que reemplaza un método o propiedad con el mismo nombre y firma en la clase base. El método base, que será reemplazado, se debe declarar como virtual, abstract u override: no es posible reemplazar un método no virtual o estático de esta forma. El método o la propiedad reemplazados y aquellos que se reemplazan deben tener los mismos modificadores de nivel de acceso.

El ejemplo siguiente muestra un método virtual denominado StepUp que es reemplazado en una clase derivada con el modificador que lo reemplaza:

public class CountClass 
{
    public int count; 

    public CountClass(int startValue)  // constructor
    {
        count = startValue;
    }

    public virtual int StepUp()
    {
        return ++count;
    }
}

class Count100Class : CountClass 
{
    public Count100Class(int x) : base(x)  // constructor 
    {
    }

    public override int StepUp()
    {
        return ((base.count) + 100);
    }
}

class TestCounters
{
    static void Main()
    {
        CountClass counter1 = new CountClass(1);
        CountClass counter100 = new Count100Class(1);

        System.Console.WriteLine("Count in base class = {0}", counter1.StepUp());
        System.Console.WriteLine("Count in derived class = {0}", counter100.StepUp());
    } 
}

Cuando se ejecuta este código, se observa que el constructor de la clase derivada utiliza el cuerpo del método proporcionado en la clase base, lo que permite inicializar el recuento de miembros sin duplicar el código. Éste es el resultado:

Count in base class = 2

Count in derived class = 101

Clases abstractas

Una clase abstracta declara uno o más métodos o propiedades como abstractos. La clase que declara dichos métodos no les proporciona una implementación, aunque una clase abstracta también puede contener métodos no abstractos, es decir, métodos para los que se ha proporcionado una implementación. No se puede crear directamente una instancia de una clase abstracta; sólo se puede crear una instancia de una clase derivada. Estas clases derivadas deben proporcionar implementaciones para todos los métodos y propiedades abstractos, mediante la palabra clave override, a menos que el miembro derivado se declare abstracto.

El ejemplo siguiente declara una clase abstracta Employee. También se crea una clase derivada denominada Manager, que proporciona una implementación del método abstracto Show() definido en la clase Employee:

public abstract class Employee 
{ 
    protected string name;

    public Employee(string name)  // constructor
    { 
        this.name = name;
    }

    public abstract void Show();  // abstract show method
}

public class Manager: Employee
{ 
    public Manager(string name) : base(name) {}  // constructor

    public override void Show()  //override the abstract show method
    {
        System.Console.WriteLine("Name : " + name);
    }
}

class TestEmployeeAndManager
{ 
    static void Main()
    { 
        // Create an instance of Manager and assign it to a Manager reference:
        Manager m1 = new Manager("H. Ackerman");
        m1.Show();

        // Create an instance of Manager and assign it to an Employee reference:
        Employee ee1 = new Manager("M. Knott");
        ee1.Show();  //call the show method of the Manager class
    } 
}

Este código invoca la implementación del método Show() proporcionado por la clase Manager e imprime el nombre del empleado en pantalla. Éste es el resultado:

Name : H. Ackerman

Name : M. Knott

Interfaces

Una interfaz es un tipo de clase esqueleto que contiene firmas de método pero no incluye ninguna implementación de método. De esta manera, las interfaces son como clases abstractas que contienen sólo métodos abstractos. Las interfaces de C# son muy similares a las de Java y funcionan de manera muy similar.

Todos los miembros de una interfaz son públicos por definición y una interfaz no puede contener constantes, campos (miembros de datos privados), constructores, destructores ni ningún tipo de miembro estático. El compilador generará un error si se especifica un modificador para los miembros de una interfaz.

Las clases se pueden derivar de una interfaz para implementar esa interfaz. Estas clases derivadas deben proporcionar implementaciones para todos los métodos de la interfaz, a menos que la clase derivada se declare abstracta.

Una interfaz se declara de forma idéntica en Java. En una definición de interfaz, una propiedad indica sólo su tipo y si es de sólo lectura, sólo escritura o de lectura y escritura únicamente por medio de las palabras clave get y set. La interfaz siguiente declara una propiedad de sólo lectura:

public interface ICDPlayer
{ 
    void Play();  // method signature
    void Stop();  // method signature

    int FastForward(float numberOfSeconds);

    int CurrentTrack  // read-only property
    {
        get;
    } 
}

Una clase se puede heredar de esta interfaz utilizando dos puntos, en lugar de la palabra clave implements de Java. La clase que se implementa debe proporcionar definiciones para todos los métodos y cualquier descriptor de acceso de la propiedad necesario, del siguiente modo:

public class CDPlayer : ICDPlayer
{
    private int currentTrack = 0;

    // implement methods defined in the interface
    public void Play()
    {
        // code to start CD...
    }

    public void Stop()
    {
        // code to stop CD...
    }

    public int FastForward(float numberOfSeconds)
    {
        // code to fast forward CD using numberOfSeconds...

        return 0;  //return success code
    }

    public int CurrentTrack  // read-only property
    { 
        get 
        { 
            return currentTrack; 
        } 
    }

    // Add additional methods if required...
}

Implementar múltiples interfaces

Una clase puede implementar múltiples interfaces mediante la sintaxis siguiente:

public class CDAndDVDComboPlayer : ICDPlayer, IDVDPlayer

Si una clase implementa más de una interfaz donde hay ambigüedad en los nombres de los miembros, se resuelve utilizando el calificador completo del nombre de la propiedad o método. Es decir, la clase derivada puede resolver el conflicto si se utiliza el nombre completo del método para indicar a qué interfaz pertenece, como en ICDPlayer.Play().

Vea también

Conceptos

Guía de programación de C#

Referencia

Herencia (Guía de programación de C#)

Otros recursos

Lenguaje de programación C# para desarrolladores de Java