7.-
HERENCIA
Y JERARQUIA DE CLASES
7.1
Introducción
Una
de las propiedades más importantes de la POO es la abstracción de datos. Esta
propiedad se manifiesta esencialmente en la encapsulación, que es la
responsable de gestionar la complejidad de grandes programas al permitir la
definición de nuevos tipos de datos: las clases.
Sin embargo, la clase no es suficiente por sí sola para soportar diseño y
programación orientada a objetos. Se necesita un medio para relacionar unas
clases con otras. Un medio son las clases anidadas, pero sólo se resuelve
parcialmente el problema, ya que las clases anidadas no tienen las características
requeridas para relacionar totalmente una clase con otra. Se necesita un
mecanismo para crear jerarquías de clases en las que la clase A sea afín a la
clase B, pero con la posibilidad de poder añadirle también características
propias. Este mecanismo es la herencia.
La herencia es, seguramente, la
característica más potente de la POO, después de las clases. Por herencia
conocemos el proceso de crear nuevas clases, llamadas clases derivadas, a partir
de una clase base.
En C++ la herencia se manifiesta con la creación de un tipo definido por el
usuario (clase), que puede heredar las características de otra clase ya
existente o derivar las suyas a otra nueva clase. Cuando se hereda, las clases
derivadas reciben las características (estructuras de datos y funciones) de la
clase original, a las que se pueden añadir nuevas características o modificar
las características heredadas. El compilador hace realmente una copia de la
clase base en la nueva clase derivada y permite al programador añadir o
modificar miembros sin alterar el código de la clase base.
La derivación de clases consigue la reutilización efectiva del código de la
clase base para sus necesidades. Si se tiene una clase base totalmente depurada,
la herencia ayuda a reutilizar ese código en una nueva clase. No es necesario
comprender el código fuente de la clase original, sino sólo lo que hace.
7.2
Clases
derivadas
En
C++, la herencia simple se realiza tomando una clase existente y derivando
nuevas clases de ella. La clase derivada hereda las estructuras de datos y funciones de la
clase original. Además, se pueden añadir nuevos miembros a las clases
derivadas y los miembros heredados pueden ser modificados.
Una clase utilizada para derivar nuevas clases se denomina
clase base, clase padre, superclase o ascendiente. Una clase creada de otra clase se denomina
clase derivada o subclase.
Se pueden construir jerarquías de clases, en las que cada clase sirve como
padre o raíz de una nueva clase.
7.3
Conceptos
fundamentales de derivación
C++
utiliza un sistema de herencia jerárquica. Es decir, se hereda una clase de otra, creando
nuevas clases a partir de las clases ya existentes. Sólo se pueden heredar
clases, no funciones ordinarias n variables, en C++.
Una clase derivada hereda todos los
miembros dato excepto, miembros dato estáticos, de cada una de sus clases base.
Una clase derivada hereda las funciones miembro de su clase base. Esto significa
que se hereda la capacidad para llamar a funciones miembro de la clase base en
los objetos de la clase derivada.
Los siguientes elementos de la clase no se heredan:
|
-
Constructores |
|
-
Destructores |
|
-
Funciones amigas |
|
-
Funciones estáticas de la clase |
|
-
Datos estáticos de la clase |
|
-
Operador de asignación sobrecargado |
Las
clases base diseñadas con el objetivo principal de ser heredadas por otras se
denominan clases abstractas. Normalmente, no se crean instancias a partir de
clases abstractas, aunque sea posible.
7.4
La herencia
en C++
En
C++ existen dos tipos de herencia: simple y
múltiple. La herencia simple
es aquella en la que cada clase derivada hereda de una única clase. En herencia
simple, cada clase tiene un solo ascendiente. Cada clase puede tener, sin
embargo, muchos descendientes.
La herencia múltiple es aquella en la
cual una clase derivada tiene más de una clase base. Aunque el concepto de
herencia múltiple es muy útil, el diseño de clases suele ser más complejo.
7.5
Creación de
una clase derivada
Cada
clase derivada se debe referir a una clase base declarada anteriormente.
La declaración de una clase derivada tiene la siguiente sintaxis:
class
clase_derivada:<especificadores_de_acceso> clase_base
{...};
Los especificadores de acceso pueden ser:
public, protected o private.
7.6
Clases de
derivación
Los
especificadores de acceso a las clases base definen los posibles tipos de
derivación: public, protected
y private. El tipo de acceso a la
clase base especifica cómo recibirá la clase derivada a los miembros de la
clase base. Si no se especifica un acceso a la clase base, C++ supone que su
tipo de herencia es privado.
-
Derivación pública (public).
Todos los miembros public y protected de la clase base son accesibles en la
clase derivada, mientras que los miembros private de la clase base son siempre
inaccesibles en la clase derivada.
-
Derivación privada (private).
Todos los miembros de la clase base se comportan como miembros privados de la
clase derivada. Esto significa que los miembros public y protected de la clase
base no son accesibles más que por las funciones miembro de la clase derivada.
Los miembros privados de la clase siguen siendo inaccesibles desde la clase
derivada.
-
Derivación protegida (protected).
Todos los miembros public y protected de la clase base se comportan como
miembros protected de la clase derivada. Estos miembros no son, pues, accesibles
al programa exterior, pero las clases que se deriven a continuación podrán
acceder normalmente a estos miembros (datos o funciones).
7.7
Constructores
y destructores en herencia
Una
clase derivada puede tener tanto constructores como destructores, aunque tiene
el problema adicional de que la clase base puede tomar ambas funciones miembro
especiales.
Un constructor o destructor definido en la clase base debe estar coordinado con
los encontrados en una clase derivada. Igualmente importante es el movimiento de
valores de los miembros de la clase derivada a los miembros que se encuentran en
la base. En particular, se debe considerar cómo el constructor de la clase base
recibe valores de la clase derivada para crear el objeto completo.
Si un constructor se define tanto para la clase base como para la clase
derivada, C++ llama primero al constructor base. Después que el constructor de
la base termina sus tareas, C++ ejecuta el constructor derivado.
Cuando una clase base define un constructor, éste se debe llamar durante la
creación de cada instancia de una clase derivada, para garantizar la buena
inicialización de los datos miembro que la clase derivada hereda de la clase
base. En este caso la clase derivada debe definir a su vez un constructor que
llama al constructor de la clase base proporcionándole los argumentos
requeridos.
Un constructor de una clase derivada debe utilizar un mecanismo de pasar
aquellos argumentos requeridos por el correspondiente constructor de la clase
base.
derivada::derivada(tipo1
x, tipo2 y):base (x,y) {...}
Otro
aspecto importante que es necesario considerar es el orden en el que el
compilador C++ inicializa las clases base de una clase derivada. Cuando El
compilador C++ inicializa una instancia de una clase derivada, tiene que
inicializar todas las clases base primero.
Por definición, un destructor de clases no toma parámetros. Al contrario que
los constructores, una función destructor de una clase derivada se ejecuta
antes que el destructor de la clase base.
7.8
Redefinición
de funciones miembro heredadas
Se
pueden utilizar funciones miembro en una clase derivada que tengan el mismo
nombre que otra de una clase base.
La redefinición de funciones se realiza mediante funciones miembro
sobrecargadas en la clase derivada. Una función miembro redefinida oculta todas
las funciones miembro heredadas del mismo nombre. Por tanto cuando en la clase
base y en la clase derivada hay una función con el mismo nombre en las dos, se
ejecuta la función de la clase derivada.
7.9
Herencia
múltiple
Es
la propiedad con la cual una clase derivada puede tener más de una clase base. Es más
adecuada para definir objetos que son compuestos, por naturaleza, tales como un
registro personal, un objeto gráfico.
Sólo es una extensión de la sintaxis de la clase derivada. Introduce una
cierta complejidad en el lenguaje y el compilador, pero proporciona grandes
beneficios.
class ejemplo: private base1, private base2 {...};
La
aplicación de clases base múltiples introduce un conjunto de ambigüedades a
los programas C++. Una de las más comunes se da cuando dos clases base tienen
funciones con el mismo nombre, y sin embargo, una clase derivada de ambas no
tiene ninguna función con ese nombre. ¿ Cómo acceden los objetos de la clase
derivada a la función correcta de la clase base ? El nombre de la función no
es suficiente, ya que el compilador no puede deducir cuál de las dos funciones
es la invocada.
Si se tiene una clase C que se deriva de dos clases base A y B, ambas con una
función mostrar() y se define un objeto de la clase C:
C objetoC, la manera de resolver la ambigüedad es utilizando el operador
de resolución de ámbito:
objetoC.A::mostrar(); //se refiere a la
versión de //mostrar() de la clase A
objetoC.B::mostrar(); //se refiere a la versión de //mostrar() de la clase B
7.10
Constructores
y destructores en herencia múltiple
El
uso de funciones constructor y destructor en clases derivadas es más complejo
que en una clase simple. Al crear clases derivadas con una sola clase base, se
observa que el constructor de la clase base se llama siempre antes que el
constructor de la clase derivada.
Además se dispone de un mecanismo para pasar los valores de los parámetros
necesarios al constructor base desde el constructor de la clase derivada. Así,
la sentencia siguiente pasa el puntero a carácter x y el valor del entero y a
la clase base:
derivada (char
*x, int y, double z): base(x,y)
Este
método se expande para efectuar más de una clase base.La sentencia:
derivada (char *x,int y, double z): base1(x),base2(y)
Llama
al constructor base1 y pasa el valor de cadena de caracteres x y al constructor
base2 le proporciona un valor entero y. Como con la herencia simple, los
constructores de la clase base se llaman antes de que se ejecuten al constructor
de la clase derivada. Los constructores se llaman en el orden en que se declaran
las clases base.
De modo similar, las relaciones entre el destructor de la clase derivada y el
destructor de la clase base se mantiene. El destructor derivado se llama antes
de que se llame a los destructores de cualquier base. Los destructores de las
clases base se llaman en orden inverso al orden en que los objetos base se
crearon.
7.11
Herencia
repetida
La
primera regla de la herencia múltiple es que una clase derivada no puede
heredar más de una vez de una sola clase, al menos no directamente. Sin
embargo, es posible que una clase se pueda derivar dos o más veces por caminos
distintos, de modo que se puede reprtir una clase. La propiedad de recibir por
herencia una misma clase más de una vez se conoce como herencia repetida.
7.12
Clases base
virtuales
Una
clase base virtual es una clase que es compartida por otras clases base con
independencia del número de veces que esta clase se produce en la jerarquía de
derivación. Suponer , por ejemplo, que la clase T se deriva de las clases C y D
cada una de las cuales se deriva de la clase A. Esto significa que la clase T
tiene dos instancias de A en su jerarquía de derivación. C++ permite
instancias múltiples de la misma clase base. Sin embargo, si sólo se desea una
instancia de la clase A para la clase T, entonces se debe declarar A como una
clase base virtual.
Las clases base virtuales proporcionan un método de anular el mecanismo de
herencia múltiple, permitiendo especificar una clase que es una clase base
compartida.
Una clase derivada puede tener instancias virtuales y no virtuales de la misma
clase base.
|