[C# Básico] ¿Que es la herencia?

Hola a todos! Despues de la buena acogida que tuvo la primera entrega de C# Básico (dedicada a las interfaces), me gustaría abordar hoy una de las cuestiones que se pusieron en los comentarios: ¿Qué es la herencia? De nuevo os recuerdo que esta serie es vuestra: no tengáis reparos en pedir posts de algún tema en concreto o aclaraciones e intentaré contestaros dentro de mis conocimientos 🙂

En la wikipedia se define herencia como: En orientación a objetos la herencia es el mecanismo fundamental para implementar la reutilización y extensibilidad del software. A través de ella los diseñadores pueden construir nuevas clases partiendo de una jerarquía de clases ya existente (comprobadas y verificadas) evitando con ello el rediseño, la remodificación y verificación de la parte ya implementada. La herencia facilita la creación de objetos a partir de otros ya existentes, obteniendo características (métodos y atributos) similares a los ya existentes.

MMMmm… no se que queréis que os diga: a mi no me queda muy claro que significa exactamente esta definición de herencia, así que vamos a buscar una que, aunque quizá no sea tan correcta sea, al menos, más entenedora. Pero antes aclaremos…

¿Que es una clase?

En muchos escritos sobre POO se define clase como una abstracción de un objeto, una plantilla para crear objetos, una definición en base a la que crear objetos, etc… Esto nos lleva a buscar la definición de objeto, para encontrarnos, muchas veces, que se nos define como la instancia de una clase. Pues vaya.

Una clase representa un concepto. Cuando desarrollamos un programa lo hacemos para solucionar una cierta necesidad de negocio, y por lo tanto debemos modelar los conceptos de negocio necesarios. Cada concepto que modelemos es una clase. Si p.ej. estamos creando un software para la creación de mesas, el concepto de mesa seguramente tendrá sentido y nos aparecerá la clase Mesa. Si estamos creando un software de dibujo, serán otros conceptos los que necesitaremos y así nos aparecerán la clase Círculo o Rectángulo. Las clases no sólo representan conceptos de negocio, también conceptos “más técnicos” como pueden ser un fichero de log, o un gestor de transacciones. Entonces, si las clases representan conceptos…

Relaciones entre clases son relaciones entre conceptos

Lógico: si las clases representan conceptos (ideas) las relaciones entre clases son relaciones entre conceptos. Y que relación puede haber entre dos conceptos? Pues que un concepto represente una idea más específica de otro concepto (o al revés que un concepto represente una idea más general que otro).

P. ej. todos estaremos de acuerdo que el concepto de Mamífero es más específico que el concepto de Animal o que el concepto de Coche es más específico que el concepto de Vehículo. Pues bien, esta relación, en a la programación orientada a objetos se traduce en la relación de generalización (en este caso Vehículo es la clase general, clase base o superclase mientras que Coche es la clase derivada, clase hija o subclase)… sí, en POO nos gusta poner nombres raros a las cosas 🙂

Una relación de generalización trae varias consecuencias y una de ellas es precisamente la herencia: la clase derivada hereda todos los miembros (campos, propiedades y métodos) de la clase base.

Eso tiene su lógica, imaginad una clase Vehículo tal que:

class Vehiculo
{
public int Velocidad { get { ... } }
public void Acelerar (int delta) { ...}
public void Frenar (int vf) { ... }
public void Girar (int angulos) { ... }
}

La clase Vehiculo representa un concepto de algo que tiene una determinada Velocidad y que puede Acelerar, Frenar y Girar. Ahora bien, si nos aparece el concepto de Coche y lo modelamos como una nueva clase, es de esperar que un Coche también tenga una Velocidad, pueda Acelerar, pueda Frenar y pueda Girar… Para que vamos a codificar de nuevo todos esos métodos si ya los hemos definido (codificado, recordad que una clase implementa un concepto) para Vehiculo?

Este es un punto importante: la herencia no es herencia sólo de interfaz sinó también de implementación. Es decir, si tengo mi clase:

class Coche : Vechiculo
{
public string Marca { get { ... } }
public string Modelo { get { ... } }
}

Un Coche es un Vehículo que además tiene una Marca y un Modelo. Así yo puedo hacer:

Coche c = new Coche();
c.Acelerar(100);
// A que velocidad va mi coche?
int velocidad = c.Velocidad;
// Es un SEAT?
if (c.Marca == "SEAT") { ... }

Fijaos que puedo llamar a la propiedad Marca (definida en la propia clase Coche) para evaluar de que marca es mi coche, pero también puedo llamar al método Acelerar y a la propiedad Velocidad

Así pues la clase Coche obtiene “una copia” de todos los métodos y propiedades de la clase de la cual deriva (su clase base) y además puede añadir métodos o propiedades nuevos. Eso es, ni más ni menos lo que entendemos por herencia.

Es importante que os quedéis con la idea de que la clase derivada obtiene una copia de los métodos y propiedades heredados porque así podréis entender el…

Problema de la herencia múltiple

C# (al igual que Java) es un lenguaje con herencia simple. Eso significa que una clase sólo puede derivar de una clase base a la vez. P.ej. esto en C# no es válido:

class VehiculoAereo { ... }
class VehiculoMaritimo { ... }
// Error: Herencia múltiple no soportada en C# (no lo está en .NET en general)
class Hidroavion : VehiculoAereo, VehiculoMaritimo { ... }

Existen otros lenguajes (como C++) que soportan herencia múltiple y donde lo anterior seria válido. La teoría de programación orientada a objetos no pone restricciones al respecto… En muchos libros se menciona esa limitación aduciendo simplemente “que la herencia múltiple es peligrosa”… pero, por que?

Es difícil contar exactamente el problema en el que puede (y subrayo el puede) incurrir la herencia múltiple sin entrar en aspectos demasiado técnicos sobre como los compiladores implementan la herencia realmente, pero el motivo por el cual la herencia múltiple es peligrosa se llama “Herencia en diamante”. Básicamente el problema de la herencia en diamante se produce cuando una clase D, hereda de dos clases B y C, las cuales ambas heredan de A. Imaginad ahora que:

  • La clase A define un método FooA()
  • La clase B (que hereda de A) obtiene una copia del método FooA()
  • La clase C (que hereda de A) obtiene una copia del método FooA()
  • La clase D que hereda de B y de C… obtiene dos copias del método FooA (el de B y el de C). Pero una clase no puede tener dos métodos FooA() con el mismo nombre y parámetros… así que eso da error.

Los lenguajes que soportan herencia múltiple (como C++) abordan ese problema mediante la llamada herencia virtual pero otros lenguajes optan por eliminar el problema de raiz impidiendo la herencia múltiple. Entre estos lenguajes están todos los de .NET (C#, VB.NET, …) o Java entre ellos.

No tener herencia múltiple puede suponer una limitación y en algún momento puede serlo, pero nunca es muy grave: muchos diseños que requieren herencia múltiple pueden repensarse para usar sólo herencia simple. La clave está en que la mayoría de veces realmente no queremos herencia múltiple sinó polimorfismo múltiple. El polimorfismo múltiple está soportado en C# (y en .NET en general) mediante el uso de interfaces: Una clase puede implementar uno o más interfaces.

Nota: En el contexto de este post polimorfismo significa poder usar un objeto de una clase CD que derive de CB, o que implemente una interfaz IX en cualquier sitio donde se espera un objeto de la clase base CB o de la interfaz IX.

Redefinición de métodos

Imaginad ahora la siguiente jerarquía de clases:

class Rectangulo
{
public Color Color { get; set; }
public void Dibujar() { ... }
}

class RectanguloPintado : Rectangulo
{
public Color ColorFondo { get; set;}
}

Tenemos una clase Rectangulo que define una propiedad llamada Color (cuyo tipo es un objeto de una clase Color) y un método Dibujar. Luego tenemos una clase derivada RectanguloPintado que añade otra propiedad ColorFondo y obtiene por herencia la propiedad Color y el método Dibujar().

Pero claro… un RectanguloPintado no se dibuja como un Rectangulo verdad? Cuando nos encontramos en este caso es cuando debemos usar la redefinición de métodos: Es decir modificar el método heredado en la clase derivada con el código necesario para que funcione correctamente (en nuestro caso dibujar el rectángulo pintado).

Pero ojo, y eso es importante, la clase base debe estar bien pensada y debe preveer que va a ser extendida y que dicho método puede ser redifinido: la clase base (Rectangulo) debe declarar el método Dibujar como virtual. Eso es algo que la gente que viene de Java le cuesta entender, puesto que en Java todos los métodos son virtuales por defecto. En C# (y en VB.NET) debemos explicitar que un método es virtual. Recordad el significado de virtual, es: Si alguien deriva de esta clase puede redefinir este método si lo necesita.

Una vez la clase base define el método como virtual, la clase derivada puede redefinir el método:

class Rectangulo
{
public Color Color { get; set;}
public virtual void Dibujar() { ... }
}

class RectanguloPintado : Rectangulo
{
public ColorFondo {get; set;}
public override void Dibujar()
{
base.Dibujar();
// Pintamos...
}
}

Fijaos en cuatro aspectos clave:

  1. El método Dibujar está declarado como virtual en la clase base
  2. El método Dibujar está declarado como override en la clase derivada
  3. El método de la clase derivada debe llamarse exactamente igual y tener los mismos parámetros, valor de retorno y visibilidad que el método de la clase base.
  4. La llamada a base.Dibujar() lo que hace es llamar al método Dibujar() de la clase base. En este caso lo usamos porque para dibujar un rectángulo pintado lo que hacemos es dibujar el rectángulo primero y luego pintarlo. Para dibujar el rectángulo, en lugar de duplicar el código del método Dibujar() de la clase Rectangulo es mucho mejor llamara  a dicho código. Y eso es lo que se puede hacer con base. Obviamente el uso de base no es obligatorio (en según que casos el método redefinido tiene una implementación totalmente distinta y no se puede aprovechar el método de la clase base).

En fin… espero que este post os ayude a comprender un poco más que es la herencia y como funciona… Como siempre comentarios, dudas son bienvenidos! 😉

Un saludo!

12 comentarios sobre “[C# Básico] ¿Que es la herencia?”

  1. Enhorabuena por esta serie de post de introducción, si me lo permites, me gustaría recomendar el libro Object-Oriented Software Construction de Bertrand Meyer, aunque solo sea para la lectura de los capítulos clave, es una auténtica Biblia en lo que a Programación Orientada a Objetos se refiere.

    Mucha gente te va a agradecer estos posts 🙂

  2. Para mí, partiendo de que la orientación a objeto no es más que la representación del mundo real, buscaría la definición de [Herencia] directamente en el RAE 🙂

    Herencia:

    «…Rasgo o rasgos morales, científicos, ideológicos, etc. (Esto no es más que comportamiento), que, habiendo caracterizado a alguien (Propiedades de ese alguien), continúan advirtiéndose en sus descendientes o continuadores. (El descendiente o continuador es el alguien que hereda características y comportamiento)…»

    Cuando tengo una duda sobre alguna definición en POO, siempre he podido aclararme buscando su equivalente en el mundo real.

    Buen artículo… 😉 Gracias….

  3. @Vicente
    Efectivamente este libro es «una de las biblias», pero ojo que no es de lectura fácil… 🙂

    @Omar
    Pues mira tu por donde nunca se me había ocurrido buscar las definiciones de términos de orientación a objetos en el diccionario de la RAE! 😉

    @Gabriel, Norberto
    Gracias! De esto se trata, que sirva a los que empiezan en esto de la orientación a objetos!

    Muchas gracias a todos por vuestros comentarios!

  4. Solo un apunte, personalmente no me gusta nada que en c# no se incorpore herencia múltiple aunque seguramente existan razones de peso para ello como los que comentas, en cualquier caso la herencia múltiple se puede ‘simular’ con la utilización de Interfaces.

    Un saludo.

  5. @Juan
    Bueno… apuesto a que vienes de C++ 🙂 A muchos que venimos de C++ no nos gusta que no haya herencia múltiple. Reconozco que usando interfaces la mayoría de veces no hay problema, aunque es cierto que me he encontrado con algunos casos donde «me hubiera gustado tener herencia múltiple». Pero no la he echado mucho en falta… Hay otros aspectos de C++ que preferiría tener antes, como p.ej. referencias const (ale, ya lo he vuelto a decir :P).

    @Jorge
    Me apunto lo de delegados… Veo que hay bastantes dudas en general sobre ellos, así que a ver si puedo destriparlos en el próximo post! 😉

    Gracias a todos por vuestros comentarios!!! 😀

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *