universidad salesiana de bolivia ingenieria de

Anuncio
UNIVERSIDAD SALESIANA DE BOLIVIA
INGENIERIA DE SISTEMAS
DOSSIER
PROGRAMACION III
DOCENTE
LIC. PATRICIA PALACIOS ZULETA
PARALELO A2
I I- 2011
1
INDICE
Pag.
UNIDAD I
CONCEPTOS FUNDAMENTALES DE PROGRAMCION ORIENTADA A OBJETOS.
Introducción………………………………………………………………………………………..
Programación Orientada a Objetos………………………………………………...……………
Breve historia de la P.O.O....................................................................................................
Conceptos básicos...............................................................................................................
Definición de objeto..............................................................................................................
Herencia................................................................................................................................
Abstracción............................................................................................................................
Encapsulación.......................................................................................................................
Polimorfismo..........................................................................................................................
Función Amiga.......................................................................................................................
1
3
5
7
10
13
16
16
17
17
UNIDAD II
PRINCIPIOS DE PROGRAMACION ORIENTADA A OBJETOS CON C++ Y JAVA
Introducción..........................................................................................................................
Historia y características del C++..........................................................................................
Características del C++.........................................................................................................
Abstracción de datos............................................................................................................
Clases y objetos en C++........................................................................................................
Envió de mensajes...............................................................................................................
Encapsulamiento..................................................................................................................
Constructores y destructores................................................................................................
Historia del Java....................................................................................................................
Ejercicios................................................................................................................................
18
18
19
20
22
22
24
27
28
32
UNIDAD III
SOBRECARGA
Sobrecarga............................................................................................................................
Sobrecarga de funciones.......................................................................................................
Sobrecarga de operadores....................................................................................................
Ejercicios................................................................................................................................
33
33
36
39
UNIDAD IV
HERENCIA
Introducción...........................................................................................................................
Herencia................................................................................................................................
Herencia Simple.....................................................................................................................
Control de acceso a la Clase base.........................................................................................
Constructores en Herencia.....................................................................................................
Herencia Múltiple....................................................................................................................
2
42
42
42
45
50
52
UNIDAD V
FUNCIONES VIRTUALES Y POLIMORFISMO
Introducción.........................................................................................................................
Ligadura..............................................................................................................................
Tipos de Ligadura................................................................................................................
Funciones Virtuales.............................................................................................................
Polimorfismo........................................................................................................................
Ejemplos..............................................................................................................................
57
57
60
60
66
67
UNIDAD VI
PLANTILLAS Y TRATAMIENTO DE EXCEPCIONES
Introducción..........................................................................................................................
Plantillas................................................................................................................................
Concepto de Excepciones.....................................................................................................
Lanzamiento de excepciones................................................................................................
68
68
72
74
LECTURAS COMPLEMENTARIAS
79
80
BIBLIOGRAFIA
3
I OBJETIVOS DE LA MATERIA
GENERAL
El objetivo principal radica principalmente en permitir conocer, aplicar y desarrollar el paradigma
de POO en el desarrollo de aplicaciones profesionales para la resolución de problemas, utilizando
las herramientas de software, lo cual facilitara la inserción de los profesionales en el desarrollo de
software.
ESPECÍFICOS
 Conocer algunas técnicas de ingeniería de software usadas en el diseño y la
implementación de programas en el lenguaje de programación C++ y Java.
 Aplicar las estructuras de datos más convenientes para solucionar problemas específicos.
II COMPETENCIA DE LA MATERIA
El objetivo de la asignatura es formar al alumno en los conceptos de la Programación Orientada a
Objetos, haciendo especial énfasis en sus aspectos más prácticos. Esto supone que a la
finalización del curso el alumno estará plenamente capacitado para desarrollar un programa
siguiendo el paradigma orientado a objetos como alternativa a la programación procedimental.
Para ello se introducirá al alumno en el lenguaje de programación más ampliamente usado el
lenguaje de programación C++ y Java.
COMPETENCIAS
De forma más esquemática, las competencias que adquirió el alumno son las siguientes:
1. Conocer y manipular con destreza el concepto de abstracción de datos.
2. Diseñar, especificar e implantar tipos de datos abstractos.
3. Dominar con destreza los paradigmas de la orientación por objetos.
4. Conocer técnicas para la especificación, diagramación y desarrollo de sistemas
4
TEMA I
TEMA No 1
INTRODUCCIÓN A LA PROGRAMACIÓN ORIENTADA A OBJETOS
Introducción.La programación orientada a objetos (o POO), es un paradigma de desarrollo definido y utilizado
por una serie de lenguajes de programación que busca afrontar el desarrollo de aplicaciones
desde un enfoque distinto al estructurado.
Toda la historia de los lenguajes de programación se ha desarrollado en base a una sola idea
conductora: hacer que la tarea de realizar programas para ordenadores sea cada vez lo más
simple, flexible y portable posible.
La POO supone, no solo un nuevo paso hacia ese fin, sino que además, a nuestro juicio, es el
más importante acaecido hasta el momento. Como nos comenta Eckel: "A medida que se van
desarrollando los lenguajes, se va desarrollando también la posibilidad de resolver problemas
cada vez más complejos. En la evolución de cada lenguaje, llega un momento en el que los
programadores comienzan a tener dificultades a la hora de manejar programas que sean de un
cierto tamaño y sofisticación."
Esta evolución en los lenguajes, ha venido impulsada por dos motores bien distintos:

Los avances tecnológicos

Los avances conceptuales (de planteamiento)

Los avances en cuanto a enfoque de la programación
 Evolución en cuanto a la conceptualización.-
El primer avance en metodología de programación, vino con la Programación Estructurada (en
este concepto vamos a incluir el propio y el de técnicas de Programación con Funciones
también llamado procedural, ya que ambos se hallan íntimamente relacionados, no creemos, que
se pueda concebir la programación estructurada sin el uso masivo de funciones).
5
La programación en ensamblador es lineal, es decir, las instrucciones se ejecutan en el mismo
orden en que las escribimos. Podemos, sin embargo, alterar este orden haciendo saltos desde
una instrucción a otro lugar del programa distinto a la instrucción que le sigue a la que se estaba
procesando.
 Programación lineal.Cada línea de programa debe ir precedida de un identificador (una etiqueta) para poder
referenciarla, para este ejemplo hemos utilizado números, aunque podría utilizarse cualquier otro
identificador:
1. Hacer una variable igual a 0
2. Sumar 1 a esa variable
3. Mostrar la variable
4. Si la variable es 100 -> terminar, Si_no -> saltar a 1.:
 Programación estructurada.1. Hacer una variable igual a 0
2. Mientras que sea menor que 100 -> sumar 1 y mostrarla
Lo importante aquí, es que cuando escribimos un programa usando las técnicas de programación
estructurada, los saltos están altamente desaconsejados, por no decir prohibidos; en cambio en
BASIC, por ejemplo, son muy frecuentes (todos conocemos el prolífico GOTO <nLínea>), lo que
no es nada conveniente si queremos entender algo que escribimos hace tres meses de forma
rápida y clara.
Para evitar esto, junto con la programación estructurada aparece un concepto que nos permite
abarcar programas más amplios con menor esfuerzo: el de función.
La idea es muy simple: muchas veces realizo procesos que se repiten y en los que sólo cambia
algún factor, si trato ese proceso como un subprograma al que llamo cada vez que lo necesito, y
cada vez que lo llamo puedo cambiar ese factor, estaré reduciendo el margen de error, al reducir
el número de líneas que necesito en mi programa, ya que no tengo que repetir todas esas líneas
cada vez que quiera realizar el proceso, con una sola línea de llamada al subprograma será
6
suficiente; además, de haber algún fallo en este proceso el error queda circunscrito al trozo de
código de la función.
Así, las funciones podemos entenderlas como unas cajas negras, que reciben y devuelven
valores. Solo tengo que programarlas una vez, las puedo probar por separado y comprobar que
funcionan correctamente, una vez terminadas puedo olvidarme de cómo las hice y usarlas
siempre que quiera.
Programación Orientada al Objeto.Por último, llegamos al más reciente avance, la POO, que nos ofrece mucho mayor dominio sobre
el programa liberándonos aún más de su control. Hasta ahora, el control del programa era tarea
del programador. El programador tenía que controlar y mantener en su mente cada proceso que
se realizaba y los efectos colaterales que pudieran surgir entre distintos procesos, lo que
llamamos colisiones. En POO, el programa se controla a sí mismo y la mente del programador se
libera enormemente pudiendo realizar aplicaciones mucho más complejas al exigir menor esfuerzo
de atención, ya que los objetos son entidades autónomas que se controlan (si han sido creados
correctamente) a sí mismos. Esto es posible principalmente porque los objetos nos impiden
mezclar sus datos con otros métodos distintos a los suyos.
En programación estructurada, una función trabaja sobre unos datos, y no debería modificar datos
que no le corresponde hacer, pero de eso tiene que encargarse el programador, en POO es el
propio sistema de trabajo el que impide que esto ocurra. Además, la re - usabilidad del código
escrito es mucho mayor que con el uso de funciones, y las portabilidad es también mayor.
¿Qué es la POO?
Comenzaremos dando una definición por exclusión de lo que es la POO, es decir, vamos a decir
primero qué no es la POO, para, luego, intentar dar una definición de lo que es.
LA POO no es:
No es un lenguaje. De hecho las técnicas de POO pueden utilizarse en cualquier lenguaje
conocido y los que están por venir, aunque estos últimos, al menos en los próximos años, incluirán
facilidades para el manejo de objetos. Desde luego, que en los lenguajes que prevén el uso de
objetos la implementación de las técnicas de POO resulta mucho más fácil y provechosa que los
7
otros. Pero del mismo modo a lo comentado en el punto anterior, se pueden utilizar estos
lenguajes sin que los programas resultantes tengan nada que ver con la POO
Como ya hemos comentado, la POO son un conjunto de técnicas que nos permiten incrementar
enormemente nuestro proceso de producción de software; aumentando drásticamente nuestra
productividad por un lado y permitiéndonos abordar proyectos de mucha mayor envergadura por
otros.
Usando estas técnicas, nos aseguramos la re-usabilidad de nuestro código, es decir, los objetos
que hoy escribimos, si están bien escritos, nos servirán para "siempre".
Hasta aquí, no hay ninguna diferencia con las funciones, una vez escritas, estas nos sirven
siempre. Pero es que, y esto sí que es innovador, con POO podemos re-usar ciertos
comportamientos de un objeto, ocultando aquellos otros que no nos sirven, o redefinirlos para que
los objetos se comporten de acuerdo a las nuevas necesidades.
Veamos un ejemplo simple: si tenemos un coche
y queremos que sea más
rápido, no construimos un coche nuevo; simplemente le cambiamos el carburador por otro más
potente, cambiamos las ruedas por otras más anchas para conseguir mayor estabilidad y le
añadimos un sistema turbo. Pero seguimos usando todas las otras piezas de nuestro coche.
Desde el punto de vista de la POO ¿Qué hemos hecho?
Hemos modificado dos cualidades de nuestro objeto (métodos): el carburador y las ruedas.
Hemos añadido un método nuevo: el sistema turbo.
En programación tradicional, nos hubiésemos visto obligados a construir un coche nuevo por
completo.
Dicho en términos de POO, si queremos construir un objeto que comparte ciertas cualidades con
otro que ya tenemos creado, no tenemos que volver a crearlo desde el principio; simplemente,
decimos qué queremos usar del antiguo en el nuevo y qué nuevas características tiene nuestro
nuevo objeto.
8
Aún hay más, con POO podemos incorporar objetos que otros programadores han construido en
nuestros programas, de igual modo a como vamos a una Ferretería y compramos piezas de
madera para ensamblarlas y montar una estantería o una mesa. Pero, es que además podemos
modificar los comportamientos de los objetos construidos por otros programadores sin tener que
saber cómo los han construido ellos.
Breve historia de la POO.Los conceptos de clase y herencia fueron implementados por vez primera en el lenguaje Simula
67 (el cual no es sino una extensión de otro más antiguo, llamado Algol 60), este fue diseñado en
1967 por Ole-Johan Dhal y Krysten Nygaard en la Universidad de Oslo y el Centro de
Computación Noruego (Norsk Regnesentral).
La historia de Simula, que es como se le llama coloquialmente, es tan frecuente como
desafortunada. Fue diseñado como un lenguaje de propósito general y pasó por el mundo de la
informática sin pena ni gloria durante años. Fue mucho después, con la aparición de otros
lenguajes que se basaban en estos innovadores conceptos (Smalltalk y sobretodo C++), cuando
se le reconoció a los creadores de Simula su gran mérito. Sin embargo, Simula sigue sin usarse
porque estos conceptos han sido ampliados y han aparecido otros nuevos que le dan mayor
potencia y flexibilidad a los conceptos originales de clase y herencia, conformando lo que hoy
entendemos por Programación Orientada al Objeto.
Aunque Simula fue el padre de todo este revuelo, ha sido Smalltalk quién dio el paso definitivo y
es éste el que debemos considerar como el primer lenguaje de programación orientado a objetos.
Smalltalk fue diseñado (cómo no) en el Palo Alto Research Center (PARC) de Xeros
Corporation's, en California.
El último gran paso, a nuestro juicio, lo dio Bjarne Stroustrup con la creación del C++, quizás el
lenguaje de programación orientado a objetos más usado actualmente. Este, fue definido en 1986
por su autor en un libro llamado The C++ Programming Language, de cita y referencia obligadas
cuando se habla de POO.
9
Fig. 1 Evolución de los lenguajes Orientados a objetos
Llegados a este punto se hace necesario aclarar que los lenguajes de POO, podemos clasificarlos
en puros e híbridos. Diremos que un lenguaje es POO puro, cuando se ajusta completamente a
los principios que esta técnica propone y contempla la posibilidad de trabajar exclusivamente con
clases. Diremos que un lenguaje es híbrido de POO y algún otro, cuando ese lenguaje, que
normalmente existía antes de la aparición de la POO, incorpora en mayor o menor medida
facilidades para trabajar con clases.
De este modo, C++ es un lenguaje POO híbrido. De hecho, C++ no incorpora todas las
características de un lenguaje POO, y no lo hace principalmente, porque es un lenguaje compilado
y ello impide que se resuelvan ciertas referencias en tiempo de compilación necesarias para dotar
a las clases de algunas de sus cualidades puramente POO (a menos que se le dote de un motor
POO interno, el cual tiene en parte).
C ha pasado a llamarse C++. Es la mejor implementación POO sobre un lenguaje compilado
existente en este momento. Su punto más conflictivo no obstante no su total implementación
POO, sino el hecho de permitir herencia múltiple, lo que, según los teóricos, no es muy
aconsejable.
10
Pascal ha sido reciclado por Borland, pasando a llamarse Delphi. Tiene una implementación POO
bastante buena, pero al no permitir sobrecarga de funciones pierde una de las características de
POO: Polimorfismo.
Basic ha pasado a llamarse Visual Basic en manos de Microsoft. Este, aun siendo uno de los
lenguajes más famosos del momento, no es un lenguaje POO completo, ya que no incluye la
característica más importante de la POO: la Herencia.
Java es la estrella de los últimos años: es pura POO, impecablemente concebido e implementado
por Sun, no dispone de sobrecarga de operadores, cualidad que de los citados aquí sólo dispone
C++, No dispone de herencia múltiple como C++.
Conceptos básicos.Estudiaremos los conceptos de Clase, Objeto, Herencia, Encapsulación, Polimorfismo, Funciones
amigas, Mensaje, Métodos, Datos y Abstracción. Estas son las ideas más básicas que todo aquel
que trabaja en POO debe comprender y manejar constantemente; es por lo tanto de suma
importancia que los entienda claramente.
 Definición de Clase.Una clase, es simplemente una abstracción que hacemos de nuestra experiencia sensible. El ser
humano tiende a agrupar seres o cosas “objetos” con características similares en grupos “clases”.
Así, aun cuando existen por ejemplo multitud de vasos diferentes, podemos reconocer un vaso en
cuanto lo vemos, incluso aun cuando ese modelo concreto de vaso no lo hayamos visto nunca. El
concepto de vaso es una abstracción de nuestra experiencia sensible.
Quizás el ejemplo más claro para exponer esto lo tengamos en las taxonomías; los biólogos han
dividido a todo ser (vivo o inerte) sobre la tierra en distintas clases.
Tomemos como ejemplo una pequeña porción del inmenso árbol taxonómico:
11
Ellos, llaman a cada una de estas parcelas reino, tipo, clase, especie, orden, familia, género, etc.;
sin embargo, nosotros a todas las llamaremos del mismo modo: clase. Así, hablaremos de la
clase animal, clase vegetal y clase mineral, o de la clase félidos y de las clases leo (león) y tigris
(tigre).
Cada clase posee unas cualidades que la diferencian de las otras. Así, por ejemplo, los vegetales
se diferencian de los minerales “entre otras muchas cosas” en que los primeros son seres vivos y
los minerales no. De los animales se diferencian en que las plantas son capaces de sintetizar
clorofila a partir de la luz solar y los animales no.
Situémonos en la clase felinos (felis), aquí tenemos varias subclases (géneros en palabras de los
biólogos): león, tigre, pantera, gato, etc. cada una de estas subclases, tienen características
comunes (por ello los identificamos a todos ellos como felinos) y características diferenciadoras
(por ello distinguimos a un león de una pantera), sin embargo, ni el león ni la pantera en abstracto
existen, existen leones y panteras particulares, pero hemos realizado una abstracción de esos
rasgos comunes a todos los elementos de una clase, para llegar al concepto de león, o de
pantera, o de felino.
La clase león se diferencia de la clase pantera en el color de la piel, y comparte ciertos atributos
con el resto de los felinos “uñas retráctiles por ejemplo” que lo diferencian del resto de los
animales. Pero la clase león, también hereda de las clases superiores ciertas cualidades: columna
12
vertebral (de la clase vertebrados) y es alimentado en su infancia por leche materna (de la clase
mamíferos).
Vemos cómo las clases superiores son más generales que las inferiores y cómo, al ir bajando por
este árbol, vamos definiendo cada vez más (dotando de más cualidades) a las nuevas clases.
Hay cualidades que ciertas clases comparten con otras, pero no son exactamente iguales en las
dos clases. Por ejemplo, la clase hombre, también deriva de la clase vertebrado, por lo que ambos
poseen columna vertebral, sin embrago, mientras que en la clase hombre se halla en posición
vertical, en la clase león la columna vertebral está en posición horizontal.
En POO existe otro concepto muy importante asociado al de clase, el de "clase abstracta". Una
clase abstracta es aquella que construimos para derivar de ella otras clases, pero de la que no se
puede instanciar. Por ejemplo, la clase mamífero, no existe como tal en la naturaleza, no existe
ningún ser que sea tan solo mamífero (no hay ninguna instanciación directa de esa clase), existen
humanos, gatos, conejos, etc. Todos ellos son mamíferos, pero no existe un animal que sea solo
mamífero.
Del mismo modo, la clase que se halla al inicio de la jerarquía de clases, normalmente es creada
sólo para que contenga aquellos datos y métodos comunes a todas las clases que de ella derivan:
Son clases abstractas. En árboles complejos de jerarquías de clases, suele haber más de una
clase abstracta.
Este es un concepto muy importante: el de "clase abstracta". Como hemos dicho, una clase
abstracta es aquella que construimos para derivar de ella otras clases, pero de la que no se puede
instanciar. Por ejemplo, la clase mamífero, no existe como tal en la naturaleza, no existe ningún
ser que sea tan solo mamífero (no hay ninguna instanciación directa de esa clase), existen
humanos, gatos, conejos, etc. Todos ellos son mamíferos, pero no existe un animal que sea solo
mamífero.
Todos estos hombres comparten las características de la clase hombre, pero son diferentes entre
sí, en estatura, pigmentación de la piel, color de ojos, complexión, etc. A cada uno de los hombres
particulares los llamamos "objetos de la clase hombre". Decimos que son objetos de tipo hombre o
que pertenecen a la clase hombre. Más técnicamente, Patricia Palacios o Leonardo da Vinci son
13
instanciaciones de la clase hombre; instanciar un objeto de una clase es crear un nuevo elemento
de esa clase, cada niño que nace es una nueva instanciación a la clase hombre.
Definición de Objeto.En POO, un objeto es un conjunto de datos y métodos
Fig. 3 Representación visual de un objeto
Los datos son lo que antes hemos llamado características o atributos, los métodos son los
comportamientos que pueden realizar.
Lo importante de un sistema POO es que ambos, datos y métodos están tan intrínsecamente
ligados, que forman una misma unidad conceptual y operacional. En POO, no se pueden desligar
los datos de los métodos de un objeto. Así es como ocurre en el mundo real.
14
Ejemplo 1:
Tomemos la clase león de la que hablamos antes y veamos cuales serían algunos de sus datos y
de sus métodos.
Ejemplo 2:
Pongamos otro ejemplo algo más próximo a los objetos que se suelen tratar en informática: un
recuadro en la pantalla.
El recuadro pertenecería a una clase a la llamaremos marco. Veamos sus datos y sus métodos.
15
Ejemplo 3:
Vayamos con otro ejemplo más. Este es para aquellos que ya tienen conocimientos de
informática, y en especial de lenguaje C o Java.
Una clase es un nuevo tipo de dato y un objeto cada una de las asignaciones que hacemos a ese
tipo de dato.
int nEdad = 30;
Hemos creado un objeto que se llama nEdad y pertenece a la clase integer (entero).
Para crear un objeto de la clase marco lo haríamos de forma muy parecida: llamando a la función
creadora de la clase marco:
Marco oCajaMsg := new Marco();
 Objeto.-
Cuando se ejecuta un programa orientado a objetos, los objetos están recibiendo, interpretando y
respondiendo a mensajes de otros objetos, Esto marca una clara diferencia con respecto a los
elementos de datos pasivos de los sistemas tradicionales. En la POO un mensaje esta asociado
con un método, de tal forma que cuando un objeto recibe un mensaje la respuesta a este mensaje
es ejecutar el método asociado
Por ejemplo cuando un usuario quiere maximizar una ventana de una aplicación de
Windows, lo hace simplemente es pulsar el botón de la misma que realiza esa acción. Esto
provoca que Windows envié un mensaje a la ventana para indicar que tiene que maximizarse.
Como respuesta a este mensaje se ejecutara el método programado parar ese fin.
16
Herencia.La herencia permite el acceso automático a la información contenida en otra clase. De esta forma
la reutilización de código esta garantizada. Con la herencia todas las clases están clasificadas en
jerarquías estrictas. Cada clase tiene su superclase (la clase superior en la jerarquía), y cada
clase puede tener una o más subclases (las clases inferiores en la jerarquía).
Como todos entendemos lo que es la herencia biológica, continuaremos con nuestro ejemplo
taxonómico del que hablábamos en el apartado anterior.
La clase león, como comentábamos antes, hereda cualidades, métodos, en lenguaje POO de
todas las clases predecesoras padres, en POO y posee métodos propios, diferentes a los del
resto de las clases. Es decir, las clases van especializándose según se avanza en el árbol
taxonómico. Cada vez que creamos una clase heredada de otra (la padre) añadimos métodos a la
clase padre o modificamos alguno de los métodos de la clase padre.
Veamos qué hereda la clase león de sus clases padre:
La clase león hereda todos los métodos de las clases padre y añade métodos nuevos que forman
su clase distinguiéndola del resto de las clases: por ejemplo el color de su piel.
Observemos ahora algo crucial que ya apuntábamos antes: dos subclases distintas, que derivan
de una misma clase padre común, pueden heredar los métodos de la clase padre tal y como estos
han sido definidos en ella, o pueden modificar todos o algunos de estos métodos para adaptarlos
a sus necesidades.
En el ejemplo que exponíamos antes, en la clase león la alimentación es carnívora, mientras que
en la clase hombre, se ha modificado éste dato, siendo su alimentación omnívora.
17
La herencia como puede intuir, es la cualidad más importante de la POO, ya que le permite
reutilizar todo el código escrito para las superclases re-escribiendo solo aquellas diferencias que
existan entre éstas y las subclases.
Veamos ahora algunos aspectos más técnicos de la herencia:
A la clase heredada se le llama, subclase o clase hija, y a la clase de la que se hereda superclase
o clase padre.
Al heredar, la clase heredada toma directamente el comportamiento de su superclase, pero puesto
que ésta puede derivar de otra, y esta de otra, etc., una clase toma indirectamente el
comportamiento de todas las clases de la rama del árbol de la jerarquía de clases a la que
pertenece.
Se heredan los datos y los métodos, por lo tanto, ambos pueden ser redefinidos en las clases
hijas, aunque lo más común es redefinir métodos y no datos.
Muchas veces las clases especialmente aquellas que se encuentran próximas a la raíz en el árbol
de la jerarquía de clases son abstractas, es decir, sólo existen para proporcionar una base para la
creación de clases más específicas, y por lo tanto no puede instanciarse de ellas; son las clases
virtuales.
Una subclase hereda de su superclase sólo aquellos miembros visibles desde la clase hija y por lo
tanto solo puede redefinir estos.
Una subclase tiene forzosamente que redefinir aquellos métodos que han sido definidos como
abstractos en la clase padre o padres.
Normalmente, como hemos comentado, se redefinen los métodos, aun cuando a veces se hace
necesario redefinir datos de las clases superiores. Al redefinir un método queremos o bien sustituir
el funcionamiento del método de la clase padre o bien ampliarlo.
En el primer caso (sustituirlo) no hay ningún problema, ya que a la clase hija la dotamos con un
método de igual nombre que el método que queremos redefinir en la clase padre y lo
18
implementamos según las necesidades de la clase hija. De este modo cada vez que se invoque
este método de la clase hija se ejecutará su código, y no el código escrito para el método
homónimo de la clase padre.
Pero si lo que queremos es ampliar el funcionamiento de un método existente en la clase padre (lo
que suele ser lo más habitual), entonces primero tiene que ejecutarse el método de la clase padre,
y después el de la clase hija. Pero como los dos métodos tienen el mismo nombre, se hace
necesario habilitar alguna forma de distinguir cuando nos estamos refiriendo a un método de la
clase hija y cuando al del mismo nombre de la clase padre.
En Java cada clase solo puede tener una superclase, lo que se denomina “Herencia Simple”. En
otros lenguajes orientados a objetos, como C++, las clases pueden tener más una superclase, lo
que se conoce como “Herencia múltiple”. En este caso una clase comparte los métodos y
propiedades de varias clases, Esta característica proporciona un poder enorme a la hora de crear
clases, pero complica excesivamente la programación. La solución a este problema en java son
las interfaces.
Una interfaz es una colección de nombres de métodos, sin incluir sus definiciones, que pueden ser
añadidas a cualquier clase para proporcionar comportamientos adicionales no incluidos en los
métodos propios o heredados.
Por ejemplo una herencia simple puede ser la clase Profesor como un tipo clase derivado de
Persona. La clase profesor es un tipo de (es-un) Persona, al que añade sus propias
características.
Persona
Es un
Profesor
19
Por ejemplo la clase Persona utilizando herencia múltiple. Como en ella se ilustra, el
GerenteVentas hereda de Gerente y Vendedor; de modo similar, EstudianteTrabajador de
Estudiante.
Persona
Empleado
Estudiante
Gerente
Estudiante
Trabajador
Gerente de
Ventas
Abstracción.-
Por medio de la abstracción conseguimos no detenernos en los detalles concretos de las cosas
que no interesen en cada momento, sino generalizar y centrarse en los aspectos que permiten
tener una visión global del problema.
Objeto Real
Objeto
Abstracto
Encapsulación.-
Esta característica permite ver un objeto como una caja negra en la que se ha introducido de
alguna manera toda la información relacionada con dicho objeto. Esto nos permite manipular los
objetos como unidades básicas, permitiendo ocultar su estructura interna.
20
La abstracción y la Encapsulación están representadas por clases. La clase es una abstracción,
porque en ella se define las propiedades o atributos de determinados conjuntos de objetos con
características comunes, y es una Encapsulación porque constituye una caja negra que encierra
tanto los datos que almacenan cada objeto como los métodos que permiten manipularlos.
Polimorfismo.-
Esta característica permite implementar múltiples formas de un mismo método, dependiendo cada
una de ellas de la clase sobre las que se realice la implementación. Esto hace que se pueda
acceder a una variedad de métodos distintos (todos con el mismo nombre) utilizando exactamente
el mismo medio de acceso.
21
TEMA II
PRINCIPIOS DE PROGRAMACIÓN ORIENTADA A OBJETOS CON
C++ Y JAVA
Introducción.Vivimos en un mundo de objetos, Cada objeto tiene atributos operaciones. Algunos objetos están
más animados que otros. Se puede agrupar los objetos en clases. Ejemplo, una naranja es un
objeto que pertenece a la clase frutas. La POO usa la notación de objeto del mundo real para
desarrollar aplicaciones. La Clase define una categoría de objetos. Cada objeto es una instancia
de una clase.
Historia y características de C++.-
El lenguaje C++ se comenzó a desarrollar en 1980. Su autor fue Bjarne. Stroustrup, también de la
ATT. Al comienzo era una extensión del lenguaje C que fue denominada C with classes. Este
nuevo lenguaje comenzó a ser utilizado fuera de la ATT en 1983. El nombre C++ es también de
ese año, y hace referencia al carácter del operador incremento de C (++). Ante la gran difusión y
éxito que iba obteniendo en el mundo de los programadores, la ATT comenzó a estandarizarlo
internamente en 1987. En 1989 se formó un comité ANSI (seguido algún tiempo después por un
comité ISO) para estandarizarlo a nivel americano e internacional.
En la actualidad, el C++ es un lenguaje versátil, potente y general. Su éxito entre los
programadores profesionales le ha llevado a ocupar el primer puesto como herramienta de
desarrollo de aplicaciones. El C++ mantiene las ventajas del C en cuanto a riqueza de operadores
y expresiones, flexibilidad, concisión y eficiencia. Además, ha eliminado algunas de las dificultades
y limitaciones del C original. La evolución de C++ ha continuado con la aparición de Java, un
lenguaje creado simplificando algunas cosas de C++ y añadiendo otras, que se utiliza para
realizar aplicaciones en Internet.
El C++ es a la vez un lenguaje procedural (orientado a algoritmos) y orientado a objetos. Como
lenguaje procedural se asemeja al C y es compatible con él, aunque ya se ha dicho que presenta
ciertas ventajas (las modificaciones menores, que se verán a continuación). Como lenguaje
orientado a objetos se basa en una filosofía completamente diferente, que exige del
programador un completo cambio de mentalidad. Las características propias de la Programación
22
Orientada a Objetos de C++ son modificaciones mayores que sí que cambian radicalmente su
naturaleza.
Características del C++. Modificaciones Menores.-
Como ya se ha dicho, el C++ contiene varias modificaciones menores sobre el C original.
Normalmente se trata de aumentar la capacidad del lenguaje y la facilidad de programación en un
conjunto de detalles concretos basados en la experiencia de muchos años. Como el ANSI C es
posterior a los primeros compiladores de C++, algunas de estas modificaciones están ya
introducidas en el ANSI C. En cualquier caso, se trata de modificaciones que facilitan el uso del
lenguaje, pero que no cambian su naturaleza.
Hay que indicar que el C++ mantiene compatibilidad casi completa con C, de forma que el viejo
estilo de hacer las cosas en C es también permitido en C++, aunque éste disponga de una mejor
forma de realizar esas tareas.
 Cambio en la extensión del nombre de los ficheros.-
El primer cambio que tiene que conocer cualquier programador es que los ficheros fuente de C++
tienen la extensión *.cpp (de C plus plus, que es la forma oral de llamar al lenguaje en inglés), en
lugar de *.c. Esta distinción es muy importante, pues determina ni más ni menos el que se utilice
el compilador de C o el de C++. La utilización de nombres incorrectos en los ficheros puede dar
lugar a errores durante el proceso de compilación.
 Comentarios introducidos en el programa.-
En C los comentarios empiezan por los caracteres /* y terminan con los caracteres */. Pueden
comprender varias líneas y estar distribuidos de cualquier forma, pero todo aquello que está entre
el /* (inicio del comentario) y el */ (fin del comentario) es simplemente ignorado por el compilador.
Algunos ejemplos de formato de comentarios son los siguientes:
23
/* Esto es un comentario simple. */
/* Esto es un comentario más largo,
distribuido en varias líneas. El
texto se suele alinear por la izquierda. */
/**************************************
* Esto es un comentario de varias *
* líneas, encerrado en una caja para *
* llamar la atención. *
**************************************/
En C++ se admite el mismo tipo de comentarios que en C, pero además se considera que son
comentarios todo aquel texto que está desde dos barras consecutivas (//) hasta el fin de la línea.
Las dos barras marcan el comienzo del comentario y el fin de la línea, el final. Si se desea poner
comentarios de varias líneas, hay que colocar la doble barra al comienzo de cada línea. Los
ejemplos anteriores se podrían escribir del siguiente modo:
// Esto es un comentario simple.
// Esto es un comentario más largo,
// distribuido en varias líneas. El
// texto se suele alinear por la izquierda.
//*************************************
// Esto es un comentario de varias *
// líneas, encerrado en una caja para *
// llamar la atención. *
//*************************************
La ventaja de este nuevo método es que no se pueden comentar inadvertidamente varias líneas
de un programa abriendo un indicador de comentario que no se cierre en el lugar adecuado.
Abstracción de datos.La abstracción es el proceso en el cual separamos las propiedades más importantes de un objeto,
de las que no lo son. Es decir, por medio de la abstracción definimos las características esenciales
de un objeto del mundo real, los atributos y comportamientos que lo definen como tal, para
después modelarlo en un objeto de software.
24
En el proceso de abstracción no debemos preocuparnos por la implementación de cada método o
atributo, solamente debemos definirlo de forma general. Por ejemplo, supongamos que deseamos
escribir un programa para representar el sistema solar, por medio de la abstracción, podemos ver
a éste sistema como un conjunto de objetos, algunos de estos objetos son los planetas, que
tienen un estado, dado por sus características físicas, digamos, tamaño, masa, entre otras, estos
objetos tendrán también comportamientos: la translación y la rotación, pueden mencionarse como
los más evidentes.
Visualizar las entidades que deseamos trasladar a nuestros programas, en términos abstractos,
resulta de gran utilidad para un diseño optimo de nuestro software, ya que nos permite
comprender más fácilmente la programación requerida.
En la tecnología orientada a objetos la herramienta principal para soportar la abstracción es la
clase. Podemos definir a una clase como una descripción genérica de un grupo de objetos que
comparten características comunes, dichas características se especificadas en sus atributos y
comportamientos. En otras palabras, una clase es un molde o modelo en donde se especifican las
características que definen a un objeto de manera general, a partir de una clase pedemos definir
objetos particulares. En programación orientada a objetos se dice que un objeto es una instancia
de la clase, es decir un objeto, es un caso particular del conjunto de objetos que comparten
características similares, definidas en la clase.
Un ejemplo servirá para clarificar estos conceptos. Definamos a la clase persona, esta clase
tendrá ciertos atributos, por ejemplo edad, sexo y nombre entre otros. Las acciones o
comportamientos que podemos definir para esta clase son, por ejemplo, respirar, caminar, comer,
etcétera. Estas son, para nuestro caso, las características que definen a nuestra clase persona.
Un objeto instanciado de está clase podría ser Patricia Palacios, de sexo Femenino y de 32 años
de edad, quién se encuentra caminando. Estos atributos son conocidos formalmente en
programación orientada a objetos como variables de instancia por que contienen el estado de un
objeto particular en un momento dado, en este caso Patricia Palacios. Así mismo, los
comportamientos son llamados métodos de instancia porque nos muestran el comportamiento de
un objeto particular de la clase.
25
Hay que tener cuidado de no confundir un objeto
con
una
clase,
una
gelatina
de
fresa
y
una
gelatina de limón son objetos de la misma clase (o
del mismo molde), pero con atributos (o sabores)
diferentes.
Clases y Objetos en C++.Una clase equivale a la generalización o abstracción de un tipo específico de objetos. Los
polígonos con tres lados iguales podrían ser generalizados, por ejemplo, en una clase que
llamaremos "trianguloEquilatero". Hablar de clase es, así, hablar de una determinada clase de
objetos. En C++ una clase ("class", según la terminología del lenguaje) es un tipo definido por el
usuario. De esta forma, y siguiendo con el ejemplo cinematográfico, el objeto abstracto "Sala de
Cine" sería implementado como la clase "SalaDeCine" en C++, habiendo definido así un nuevo
tipo de dato abstracto, que será manejado por el compilador de parecida forma a como lo hace
con los tipos predefinidos (int, char, float, etc.). Podremos, así, como ya veremos, declarar un
determinado objeto del tipo (o de la clase) "SalaDeCine".
Un objeto es una encapsulación abstracta de información, junto con los métodos o
procedimientos para manipularla. "un objeto contiene operaciones que definen su comportamiento
y variables que definen su estado entre las llamadas a las operaciones". Bueno, pensemos en un
ejemplo asequible: una sala de cine. Imaginemos un objeto del tipo "sala de cine" donde los datos,
variables o información estarían constituidos por los espectadores; imaginemos también (y esto es
importante) que los espectadores no pudieran moverse por sí solos dentro de la sala (como si
estuvieran catatónicos). ¿Quiénes serían los manipuladores, métodos, procedimientos u
operaciones encargados de manipular a los espectadores? Indudablemente los acomodadores de
la sala, aunque también el personal directivo de la misma.
Envió de mensajes.-
Un mensaje representa una acción a tomar por un determinado objeto. En el ejemplo anterior, la
orden "que comience la proyección de la película" podría ser un mensaje dirigido a un objeto del
tipo "sala de cine". Otro mensaje podría ser la orden de "desalojo de la sala" en funciones nocontinuas. Notemos que el mensaje se refiere únicamente a la orden, y no tiene que ver con la
26
forma como ésta es respondida. En C++ un mensaje equivale al PROTOTIPO de una función
miembro en la descripción de una clase. O sea, si pensamos en una posible función
"especialmente restringida a un objeto" (que en C++ se denomina función miembro) tal como
"empezarProyeccion", el mensaje estaría representado por el prototipo de tal función, y no por su
definición o implementación específica.
El "envío de un mensaje" a un objeto equivale, en C++, como ya he indicado, a una llamada a una
función miembro de la clase correspondiente al objeto. En realidad las expresiones "métodos",
"envío de mensajes", "instancias de variables", etc. pertenecen originariamente al lenguaje
Smalltalk. Volvamos al ejemplo del cine y "lancemos mensajes":
SalaDeCine cineParadox;
SalaDeCine *punteroASalaDeCine;
Hemos declarado por un lado un objeto denominado cineParadox, del tipo definido-por-el-usuario
SalaDeCine. Seguidamente hemos declarado un puntero al mismo tipo de dato abstracto.
El mensaje "que comience la proyección de la película", dirigido a la sala "Cine
Paradox", podría ser codificado así:
cineParadox.empezarProyeccion ();
Esto es, el mensaje o llamada de función se ha dirigido directamente al objeto. Pero veamos el
siguiente código:
punteroASalaDeCine = new SalaDeCine( cinePalafox );
punteroASalaDeCine->empezarProyeccion;
Vemos, pues, que la notación '.' sirve para enviar mensajes directamente a un objeto, mientras
que '->' se usa para el envío de mensajes a los objetos a través de punteros que apunten a los
mismos.
Cabría notar aquí, redundando en lo apuntado anteriormente, que el prototipo de función
empezarProyeccion () corresponde al mensaje, mientras que el método estaría constituido por la
implementación de la definición de la función miembro empezarProyeccion (). El mensaje
27
corresponde al "qué", mientras que el método corresponde al "cómo". Así, el método podría
definirse -en la clase SalaDeCine- de la forma:
void SalaDeCine::empezarProyeccion()
{
// apaga música y luces y comienza proyección
ponerMusicaDeFondo( OFF );
ponerLucesSala( OFF );
marchaProyector( ON );
}
En este caso el mensaje empezarProyeccion podría ser enviado al objeto cineParadox por un
objeto cronómetro con un horario determinado.
Pudiera parecer, por otra parte, que el método o función miembro empezarProyeccion está
compuesto por funciones del tipo usado en programación estructurada, pero en realidad se trata
de mensajes dirigidos al mismo objeto (en este caso concreto, cineParadox), dado que, por
ejemplo, el código
marchaProyector( ON );
Equivale a:
this->marchaProyector( ON );
En donde this es un puntero implícito al objeto
Encapsulamiento.-
¿Qué es, por otro lado, la encapsulación? Lo que de inmediato nos sugiere el vocablo es la acción
de encapsular, de encerrar algo en una cápsula, como los polvitos medicinales que nos venden en
las farmacias. Podemos decir que la encapsulación se refiere a la capacidad de agrupar y
condensar en un entorno con límites bien-definidos distintos elementos. El fenómeno de la
encapsulación podría darse, por ejemplo, en el conjunto de herramientas y elementos
heterogéneos que integran es un decir la caja de herramientas de un fontanero: la caja es la
cápsula y todo lo que haya dentro es lo que hemos encapsulado. Pero no nos perdamos: la
28
cualidad de "encapsulación" la aplicaremos únicamente a abstracciones: o sea, afirmar que una
caja de herramientas concreta encapsula un bocadillo, un martillo y un limón constituye una cierta
trasgresión léxica. Cuando hablemos de encapsulación en general siempre nos referiremos, pues,
a encapsulación abstracta. Las dos propiedades expuestas están, como vemos y en lo que a la
POO concierne, fuertemente ligadas. Dicho de manera informal, primero generalizamos (la
abstracción) y luego decimos: la generalización está bien, pero dentro de un cierto orden: hay que
poner límites (la encapsulación), y dentro de esos límites vamos a meter, a saco, todo lo
relacionado con lo abstraído: no sólo datos, sino también métodos, comportamientos, etc. Pero,
bueno ¿y esto es POO? Pues sí, esta es una característica fundamental de POO.
En lo que a C++ se refiere, la unidad de abstracción y encapsulación está representada por la
Clase, (class). Por un lado es una abstracción pues, de acuerdo con la definición establecida
anteriormente, es en ésta donde se definen las propiedades y atributos genéricos de
determinados objetos con características comunes (recordemos el ejemplo de la sala de cine). La
Clase es, por otro lado, una encapsulación porque constituye una cápsula o saco que encierra y
amalgama de forma clara tanto los datos de que constan los objetos como los procedimientos que
permiten manipularlos. Las Clases se constituyen, así, en abstracciones encapsuladas.
Abordemos ahora el tema en su parte cruda y echémosle un vistazo a parte del código de lo que
podría ser una descripción de una clase que denominaremos Racional:
Class Racional {
friend Racional& operator+( Racional, Racional );
// ...
private:
int numerador;
int denominador;
// ...
void simplificaFraccion();
// ...
public:
// ...
void estableceNumerador( int );
void estableceDenominador( int );
// ...
29
};
Observemos que en la clase Racional, abstracción del conjunto de los números racionales, de su
representación formal (x/y: equis dividido por y) y operaciones (a/b + c/d), aparecen encapsulados
tanto los datos internos (numerador, denominador) como las funciones miembro para el
tratamiento de éstos (el operador suma de quebrados, el procedimiento para cambiar el
denominador, el método para simplificar fracciones, etc.).
Bueno, la verdad es que en el código anterior observamos muchas más cosas: el símbolo de
comentario //, que anula lo escrito (a efectos de compilación) desde su aparición hasta el fin de la
línea; las etiquetas private y public, que determinan cómo y quién accederá a los datos y
funciones de la clase; la palabra clave friend, que indica la calificación como "amiga de la clase
Racional" de una función que suma Racionales (¿Racionales? ¿alguna forma de typedef?) y a la
que se dará el tratamiento de "operador suma" (operator +), devolviendo una "referencia" (&) a
Racional (¿referencia a Racional? ¿alguna forma de puntero?). Vale, vale: son muchas cosas en
las que todavía no podemos entrar y que en su momento veremos en detalle. Bástenos saber, por
ahora, que tal código intenta describir el comportamiento y la esencia de un tipo de datos bien
conocido por todos (el conjunto de los números racionales, de la forma x/y, donde x e y son
números enteros) pero no incluido como tipo predefinido2 por la mayoría de compiladores. Esta
carencia es la que intentamos suplir con el código expuesto: una vez descrita la clase (class), se
habrá traspasado al compilador el conocimiento de, para él, un nuevo tipo de número: el
"Racional". Retengamos de momento únicamente esta última idea pensando, como único alivio,
que cuanto más veamos una "rareza", antes dejara ésta de serlo.
Salvado el inciso, quizá puedan apreciarse mejor las características de abstracción y
encapsulación notadas en la siguiente porción de la definición de la clase SalaDeCine, tomada del
ejemplo "cinematográfico":
Class SalaDeCine {
private:
int aforoSala;
char *nombreSala;
// ...
public:
void alarmaEnCasoDeIncendio();
30
void empezarSesion();
// ...
};
La clase SalaDeCine aglutina, según lo expuesto, tanto los datos correspondientes a las salas de
cine en general (capacidad de la sala, nombre de la misma, etc.) como los métodos de respuesta
a los mensajes dirigidos a los objetos "salas de cine" (alarma por incendio, etc.). O sea, la clase
SalaDeCine es la abstracción resultante de la asimilación intelectual de todas las posibles salas
de cine, vistas o soñadas: aquí tenemos la cualidad de abstracción. La misma clase encapsula,
por otro lado, datos y funciones dentro de un límite bien definido: ¿cuál? ¿Las paredes del
edificio? ¡No, vaya! El límite es el mismo que el que separa una sala de cine de un almacén de
frutas: su propia definición, que en este caso coincide con el bloque de la clase. La cápsula esta
formada por los brazos {} que encierran el cuerpo de la clase.
Conviene notar que los tipos de datos incorporados al compilador (int, char, long, etc.) son
realmente abstracciones encapsuladas de datos y métodos. Consideremos el siguiente código:
float resta, minuendo, sustraendo;
resta = minuendo - sustraendo;
El operador (-) representa aquí la operación de substracción entre dos variables del tipo
predefinido 'float'. Pero tal operador es, realmente, un mensaje que el objeto sustraendo (un
número float) le envía al objeto minuendo (otro número float) , y el método de respuesta a tal
mensaje está implementado en la encapsulación de tipo float predefinida en el compilador, que
resta del minuendo el sustraendo y devuelve un valor de tipo float. Tal método es formalmente
diferente del que podría ser implementado para la resta de dos variables de tipo int (no hay parte
decimal, etc.), o aún de un tipo definido por el usuario (user-defined). Aclaremos esto: el
compilador sabe que una operación sobre floats no tiene por
Constructores Y Destructores.-
Un constructor es un procedimiento especial de una clase que es llamado automáticamente
siempre que se crea un objeto de esa clase. Su función es iniciar el objeto.
31
Un destructor es un procedimiento especial de una clase que es llamado Automáticamente
siempre que se destruye un objeto de esa clase. Su función es realizar cualquier tarea final en el
momento de destruir el objeto.
Historia del java.-
A pesar de lo que pueda se pueda en un principio pensar, Java no surgió inicialmente como un
lenguaje de programación orientado a la web. Los orígenes se remontan al año 1991 cuando
Mosaic (uno de los primeros browsers) o la World Wide Web no eran más que meras ideas
interesantes. Los ingenieros de Sun Microsystems estaban desarrollando un lenguaje capaz de
ejecutarse sobre productos electrónicos de consumo tales como electrodomésticos.
Simultáneamente James Gosling, el que podría considerarse el padre de Java, estaba trabajando
en el desarrollo de una plataforma software barata e independiente del hardware mediante C++.
Por una serie de razones técnicas se decidió crear un nuevo lenguaje, al que se llamó Oak, que
debía superar algunas de las deficiencias de C++ tales como problemas relacionados con la
herencia múltiple, la conversión automática de tipos, el uso de punteros y la gestión de memoria
El lenguaje Oak se utilizó en ciertos prototipos de electrónica de consumo pero en un principio no
tuvo el éxito esperado dado que la tecnología quizás era demasiada adelantada a su tiempo. No
obstante lo positivo de estos primeros intentos fue que se desarrollaron algunos de los elementos
precursores de los actuales componentes Java; componentes tales como el sistema de tiempo de
ejecución y la API (Application Program Interface
“Interfaz para la Programación de
Aplicaciones”).
En 1994 eclosionó el fenómeno web y Oak fue rebautizado como Java. En un momento de
inspiración, sus creadores decidieron utilizar el lenguaje para desarrollar un browser al que se
llamó WebRunner, que fue ensayado con éxito, arrancando en ese momento el proyecto
Java/HotJava. HotJava fue un browser totalmente programado en Java y capaz así mismo de
ejecutar código Java.
32
A lo largo de 1995 tanto Java, su documentación y su código fuente como HotJava pudieron
obtenerse para múltiples plataformas al tiempo que se introducía soporte para Java en la versión
2.0 del navegador Netscape.
La versión beta 1 de Java despertó un inusitado interés y se empezó a trabajar para que Java
fuera portable a todos los sistemas operativos existentes.
En diciembre de 1995 cuando se dio a conocer la versión beta 2 de Java y Microsoft e IBM dieron
a conocer su intención de solicitar licencia para aplicar la tecnología Java, su éxito fue ya
inevitable.
El 23 de enero 1996 se publicó oficialmente la versión Java 1.0 que ya se podía obtener
descargándola de la web.
A principios de 1997 aparece la versión 1.1 mejorando mucho la primera versión.
Java 1.2 (Java 2) apareció a finales de 1998 incorporando nuevos elementos. Según Sun esta era
la primera versión realmente profesional.
En mayo del 2000 se lanza la versión 1.3 del J2SE (Java 2 Standar Edition) y hace unos meses,
en febrero de este año, se lanzó la versión 1.4 (la versión 1.4.1 es ya una beta).
 ¿Qué es Java?
33
Java no es sólo un lenguaje de programación, Java es además un sistema de tiempo de
ejecución, un juego de herramientas de desarrollo y una interfaz de programación de aplicaciones
(API). Todos estos elementos así como las relaciones establecidas entre ellos se esquematizan
en la siguiente figura.
Fig. 2 Las interioridades de Java
El desarrollador de software escribe programas en el lenguaje Java que emplean paquetes de
software predefinidos en la API. Luego compila sus pr ogramas mediante el compilador Java y el
resultado de todo ello es lo que se denomina bytecode compilado. Este bytecode es un fichero
independiente de la plataforma que puede ser ejecutado por máquina virtual Java. La máquina
34
virtual puede considerarse como un microprocesador que se apoya encima de la arquitectura
concreta en la que se ejecuta, interactuando con el sistema operativo y el hardware, la máquina
virtual es por tanto dependiente de la plataforma del host pero no así el bytecode. Necesitaremos
ta ntas máquinas virtuales como plataformas posibles pero el mismo bytecode podrá ejecutarse
sin modificación alguna sobre todas ellas.
Así pues, las claves de la portabilidad de Java son su sistema de tiempo de ejecución y su API.
Los potentes recursos para el trabajo de ventanas y redes incluidos en la API de Java facilitan a
los programadores el desarrollo de un software que resulte a la vez atractivo e independiente de la
plataforma.
Ejemplo.-
1) Ingrese dos Números enteros y determine cual es el mayor de ellos.(Completar)
import java.io.*;
public class Ejemplo1
{
public static void main(String args[]) throws Exception
{
int a,b ,mayor;
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
{
System.out.println("Ingrese el primer numero...: " );
a= Integer.parseInt(br.readLine());
System.out.println("Ingrese el segundo numero...: " );
b= Integer.parseInt(br.readLine());
if(a > b)
{
mayor = a;
}else{
mayor = b;
}
}
}
}
35
EJERCICIOS.-
1) Ingrese 3 números enteros positivos y determine cuál de ellos es el mayor y menor.
2) Genere la siguiente serie:
X = 1/1 - 1/22 + 1/ 32 - 1/42 + - …..
3) Dado un numero entero psitivo escribir en el orden inverso.
4302
36
---------------->
2034
TEMA III
SOBRECARGA
Introducción.La sobrecarga es una propiedad que describe una característica adecuada que utiliza el mismo
nombre de operación para representar operaciones similares que se comporta de modo diferente
cuando se aplican a las clases diferentes. Por consiguiente, los nombres de las operaciones se
pueden sobrecargar, esto es, las operaciones se definen en clase diferente y pueden tener
nombres idénticos, aunque su código programa puede diferir.
Si los nombres de una operación se utilizan para nuevas definiciones en clases de una jerarquía la
operación a nivel inferior se dice que anula la operación a un nivel más alto.
Actualmente la sobrecarga solo se aplica a operaciones. Aunque es posible extender la propiedad
a atributos y relaciones especificas del modelo propuesto.
Los lenguajes de programación convencionales soportan sobrecarga para algunas de las
operaciones sobre algunos tipos de datos, como enteros, reales y caracteres. Los sistemas
orientados a objetos dan un poco más en la sobrecarga y la hacen disponible para operaciones
sobre cualquier tipo de objeto.
Sobrecarga de funciones en C++ y java. Sobrecarga en c++:
C++ permiten que sean definidas varias funciones del mismo nombre, siempre que estos nombres
de funciones indiquen diferentes conjuntos de parámetros (por lo menos en lo que se refiere a sus
tipos). Esta capacidad se llama homonimia de funciones. Cuando se llama a una función
homónima, el compilador C++ selecciona de forma automática la función correcta examinando el
número, tipos y orden de los argumentos de la llama. La homonimia de la función de utiliza para
crear varias funciones del mismo nombre, que ejecutan tares similares, sobre tipos de datos
diferentes.
37
#include<iostream.h>
// existen dos metodos con el nombre (en ese caso se realiza la sobrecarga) y nos devuelven //dos resultados con dos tipos diferente
(entero y Double).
int cuadrado(int x){return x * x;}
double cuadrado (double y) {return y * y;}
main()
{
cout << "El cuadrado de 7 es "
<< cuadrado(7)
<< "\n el cuadrado de double 7.5 es "
<< cuadrado(7.5)<< "\n";
cin.get();
return 0;
}
 Sobrecarga en Java:
Para empezar, una breve reseña acerca de la sobrecarga de métodos. Decimos que un método
ha sido “sobrecargado” cuando han sido creados múltiples métodos con el mismo nombre, pero
especificando distintos argumentos (en número o tipo).
Como ejemplo veamos este trozo de código:
class Dog {
public static void main(String[] args) {
bark();
// Imprime sin parametros
bark(100); // Imprime con parametros
}
public static void bark() {
System.out.println("no parametros");
}
public static void bark(int decibels) {
System.out.println("existen parametros");
}
}
38
En este caso, se ejecuta un método u otro dependiendo de si se especifica un parámetro en la
llamado o no.
Supongamos ahora una situación como la siguiente:
class Animal {}
class Dog extends Animal {}
class TestOverload {
public static void main (String[] args) {
Animal a1 = new Animal();
Animal a2 = new Dog();
Dog d = new Dog();
makeSound(a1);
makeSound(a2);
makeSound(d);
}
public static void makeSound(Animal a) {
System.out.println("Realiza sonidos de animal");
}
public static void makeSound(Dog d) {
System.out.println("Realiza sinidos de perro");
}
}
La razón es que Java determina qué versión del método sobrecargado va a ser llamada en
tiempo de compilación.
Así pues, aunque a referencia a2 apunta a una instancia del objeto Dog, el compilador utiliza una
referencia de tipo Animal para determinar cual de los métodos será llamado.
A continuación un ejemplo de lo peligroso que puede llegar a resultar esto. Un ejemplo en que la
salida del programa no permite distinguir cual de los métodos está siendo ejecutado:
class Animal {
public void doMakeNoise() {
System.out.println("Hacer como animal");
39
}
}
class Dog extends Animal {}
class TestOverload {
public static void main (String[] args) {
Animal a1 = new Animal();
Animal a2 = new Dog();
Dog d = new Dog();
makeSound(a1);
makeSound(a2);
makeSound(d);
}
public static void makeSound(Animal a) {
a.doMakeNoise();
}
public static void makeSound(Dog d) {
d.doMakeNoise();
}
}
Esto se debe al polimorfismo y la llamada a doMakeNoise añadida en cada uno de los métodos
makeSound.
Evidentemente, esto no es un problema si la funcionalidad final es la misma (pero en este caso se
necesitaría una reorganización de código).
Lo importante, en cualquier caso, es tener en mente que Java determina el método sobrecargado
a ejecutar en tiempo de compilación.
Sobrecarga de operadores. Sobrecarga de operadores en C++:
La sobrecarga de operadores es utilizada en la Programación Orientada a Objetos, esta hace
posible manipular objetos de clases con operadores estándar +, *, [ ], y <<. Los tipos de
operadores a tratar serán los binarios y unitarios.
Para sobrecargar un operador, y aplicarlo en operaciones entre clases, se usa la función especial
“operator” seguido del operador que se sobrecargara, la sintaxis es la siguiente:
40
tipo nombre_clase::operator # (lista de parámetros);
Donde:

tipo: es el tipo de retorno de la operación de sobrecarga, por lo general es la referencia del
objeto.

nombre_clase: es el nombre de clase en la que estamos aplicando sobrecarga.

operator: función especial para sobrecarga.

operador que estamos sobrecargando, debe ser uno de los que esta en la tabla mas
abajo.

Lista de parámetros: es la lista de objetos (en referencia) o variables que se procesaran en
la sobrecarga, si la operación es unaria no tendrá parámetros, si la
Operación es binaria tendrá un parámetro.
Como ejemplo se muestra la sobrecarga del operador suma:
numero& numero::operator + (numero &p){
numero *salida = new numero; //creando objeto de retorno
salida->n = this->n + p.n; //operacion suma
return *salida; //retorno
}
Operadores que se pueden sobrecargar
Operadores Unitarios son:
New delete new [ ] delete
++ Incremento.
-- Decremento.
() Llamada a función
[ ] Indexación
41
Ejemplo.En este ejemplo se muestra la sobrecarga de operadores binarios (suma: +), operadores unarios
(incremento: ++), y la interacción con variables normales (igualación: =). Sobre la
clase numero:
#include <iostream>
//using namespace std;
class numero {
private:
int n;
public:
numero() : n(0) {} //constructor sin parametros
numero(int a) { this->n = a; } //constructor con parametros
int ver() { return n; } //muestra valor en n
numero& operator + (numero &p); //declaracion sobrecarga operador binario suma
numero& operator =(int a); //declaracion sobrecarga operador igualacion
numero& operator ++(); //declaracion sobrecarga operador unario incremento
};
//definicion de la sobrecarga del operador de suma
numero& numero::operator + (numero &p){
numero *salida = new numero;
salida->n = this->n + p.n;
return *salida;
}
//cuerpo de la sobrecarga del operador de valoración
numero& numero::operator =(int a){
this->n=a;
return *this;
}
//definicion de la sobrecarga del operador de incremento
numero& numero::operator ++ (){
this->n ++;
return *this;
}
// prueba para la clase numero
int main(){
42
numero x(4), y(2), z; //instancias x, y, z de objeto numero
z = x + y; //implementando operador binario suma
cout << "\n suma:\n \t x(" << x.ver()<<")+y("<<y.ver()<<") = z("<<z.ver()<<")"<<endl;
z=2; //implementando operador de igualacion con un entero
cout << "\n igualacion \"z=\" \n \t z="<<z.ver()<<endl;
++z; //implementando operador unario de incremento
cout << "\n incremento \"++\" \n \t ++z="<<z.ver()<<endl;
cin.get();
return 0;
}
 Sobrecarga de operadores en java:
En java sólo existe la sobrecarga de funciones (métodos); Los operadores que existen ya vienen
sobrecargados por el compilador, (por ejemplo la “+” para sumar números o concatenar cadenas).
public class sobrecarga
{
public static void main(String args[])
{
int a , b;
String c , d;
a = 2;
b = 3;
c = "Hola ";
d ="Mundo";
/*
* El operador + es un perador sobrecargado para sumar numeros y concatenar cadenas
*/
System.out.println(a + b);
System.out.println( c + d );
}
}
Ejemplos.class Numero{
friend ostream& operator<<(ostream&salida , const Numero&a);
friend istream& operator>>(istream&salida, Numero &a);
friend Numero operator+(const Numero &a,const Numero &b);
friend Numero operator-(const Numero &a,const Numero &b);
friend Numero operator/(const Numero &a,const Numero &b);
friend Numero operator*(const Numero &a,const Numero &b);
public: Numero(double x = 0);
43
void establecer(double x); private: double x;};
//Constructor
Numero::Numero(double b)
{
establecer( b );
}
void Numero::establecer(double a)
{
x = a;
}
//Sobrecarga de operadores, mediante funciones friend
Numero operator+(const Numero &a ,const Numero &b)
{
Numero temporal;
temporal.x = a.x + b.x;
return temporal;
}
Numero operator-(const Numero &a ,const Numero &b)
{
Numero temporal;
temporal.x = a.x - b.x;
return temporal;
}
Numero operator/(const Numero &a ,const Numero &b)
{
if (b.x == 0){
cout << "error de division por 0. Detiene ejecucion." << endl;
// exit(1);
}
Numero temporal;
temporal.x = a.x / b.x;
return temporal;
}
Numero operator*(const Numero &a ,const Numero &b)
{
Numero temporal;
temporal.x = a.x * b.x;
return temporal;
}
ostream &operator<<(ostream& salida , const Numero& a)
{
44
salida << a.x ; return salida;
}
istream &operator>>(istream& entrada, Numero & a)
{
entrada >> a.x;
return entrada;
}
//Probamos la clase.int
main()
{
cout << "Tres numeros porfa: " << endl;
Numero A; cin >> A;
Numero B; cin >> B;
Numero C; cin >> C;
cout << "A = " << A << ", B = " << B << ", C = " << C << endl;
Numero resultado; resultado = A - B + C;
cout << "Resultado = A - B + C = " << resultado << endl;
resultado = A / B ;
cout << "Resultado = A / B = " << resultado << endl;
resultado = A * resultado;
cout << "Resultado = A * resultado = " << resultado << endl;
resultado = B * resultado / A;
cout << "Resultado = B * resultado / A = " << resultado << endl;
cin.get();
return 0;
}
45
TEMA IV
HERENCIA
Introducción.La herencia permite el acceso automático a la información contenida en otra clase. De esta forma
la reutilización del código está garantizada. Con la herencia todas las clases están clasificadas en
una jerarquía estricta. Cada clase tiene su superclase (la clase superior en la jerarquía).
Herencia.La herencia supone una clase base y una jerarquía de clase que contienen las clase derivadas de
la clase base. Las clases derivadas pueden heredar el código y os datos de su clase base,
añadiendo su propio código especial y datos a ellas , incluso cambiar a aquellos elementos de la
clase base que necesitan ser diferentes.
Una clase hereda característica (datos y funciones) de otra clase.
Herencia simple.-
Es cuando una clase derivada hereda de una única clase, es decir una clase derivada sólo tiene
un padre o ascendiente. Por su parte una clase base puede tener tantos descendientes como
sean necesarios sin limitación alguna. Es un sistema jerárquico en forma arborescente, similar a la
estructura de directorios de algunos sistemas operativos. La forma general de la herencia en C++
es:
class <nombre_clase_derivada>: [<acceso>] <nombre_clase_heredada> {
46
// Cuerpo de la declaración de la clase
};
El nombre_clase_heredada se refiere a una clase base declarada previamente. Ésta pude estar
ya compilada, o puede que se declare en el mismo programa que la derivada, en este segundo
caso se debe declarar la clase base antes de la derivada, o al menos declarar el nombre de la
clase base como una referencia anticipada.
El acceso puede ser private, protected o public. Si se omite se supone que el acceso es
private5, de forma que si se quiere dar un acceso public o protected se debe hacer
explícitamente.
Los elementos private de la clase base son inaccesibles para la clase derivada, sea cual se el
acceso. Si el acceso es public, todos los elementos public y protected de la clase base
seguirán siendo public y protected respectivamente en la clase derivada. Si el acceso es
private, entonces todos los elementos public y protected de la clase base pasarán a ser
elementos private de la clase derivada. Si el acceso es protected todos los miembros public y
protected de la clase base se convierten en elementos protected de la clase derivada. En la
siguiente tabla se resumen los especificadores de acceso:
Se puede añadir a la clase derivada datos y funciones miembro. Dentro de las funciones miembro
de la clase derivada se puede llamar a las funciones miembro y manejar los datos miembro que
estén en la sección pública y protegida de la clase base. En una clase derivada se heredan todos
los datos miembro de la clase base excepto los estáticos. Algunas funciones miembro no se
47
heredan de forma automática. Éstas son los constructores, el destructor, las funciones amigas, las
funciones estáticas de la clase, y el operador de asignación sobrecargado.
Se va a ilustrar lo que se ha dicho hasta el momento de la herencia en los siguientes ejemplos:
// Programa: Herencia Simple
// Fichero: HER_SIM1.CPP
#include <iostream.h>
// Se define una clase sencilla
class Primera {
protected:
int i, j;
public:
Primera() {i=j=0;}
Primera(int a, int b) {i=a; j=b;}
void Entra_ij(int a, int b) {i=a; j=b;}
void Muestra_ij (void) {cout << "\n" << i << " " << j;}
};
// Se tiene otra clase, Segunda, que es derivada de la clase Primera
// Las variables i, j de Primera pasan a ser miembros protected de
// Segunda, ya que el acceso es public
class Segunda: public Primera {
int k;
public:
void Entra_k(int a) {k=a;}
void Muestra_k (void) {cout << "\n" << k;}
};
// Se tiene otra clase, Tercera, que es derivada de la clase Segunda
// Las variables i, j de Primera pasan a ser miembros protected de
// Tercera, ya que el acceso es public. Sin embargo no tiene acceso a k de
// Segunda, ya que esta variable es private.
class Tercera: public Segunda {
public:
void f (void) { i=j=2;}
};
void main (void)
{
Primera P(1,2);
Segunda S;
Tercera T;
S.Entra_ij(3,4);
48
S.Entra_k(5);
P.Muestra_ij(); // Saca 1 2
S.Muestra_ij(); // Saca 3 4
S.Muestra_k(); // Saca 5
T.f();
T.Muestra_ij(); // Saca 2 2
T.Entra_k(3);
T.Muestra_k(); // Saca 3
S.Muestra_k(); // Saca 5
T.Entra_ij(5,6);
T.Muestra_ij(); // Saca 5 6
P.Muestra_ij(); // Saca 1 2
}
 Ventajas e inconvenientes de la derivación Privada y Protegida.-
Cuando se utiliza el especificador de acceso private o el especificador de acceso protected en la
herencia, se está asegurando que sólo las partes públicas de la clase derivada podrán ser
utilizadas por el resto del programa, y por las otras clases derivadas que se derivan a partir de
ésta.
El acceso private “corta” el acceso, no la herencia, a partir de la clase derivada. El acceso
protected “corta” el acceso, no la herencia, desde el exterior, pero no desde las clases derivadas.
Los inconvenientes llegan porque al utilizar estos tipos de accesos se está complicando el árbol
de herencia, alcanzando una mayor complejidad la determinación por parte del programador, o de
alguien que lea el programa, del derecho de acceso a los miembros de cada clase. Estos tipos de
derivaciones se emplean muy poco, hay que ser muy cuidadoso cuando se utilizan, y tener unos
buenos motivos. Se puede considerar similar a tener un miembro que es una instancia de otra
clase diferente.
Control de acceso a la clase base.Dado que una clase derivada se puede entender como un superconjunto de la clase base, que va
a contener todos los miembros de la clase base, una instancia de la clase derivada se puede
emplear de forma automática como si fuera una instancia de la clase base. El caso contrario no es
cierto, no se puede tratar un objeto de la clase base como si fuera un objeto de una clase
derivada, ya que el objeto de la clase base no tiene todos los miembros de la clase derivada.
49
El siguiente programa muestra como funciona el tema de las conversiones de un objeto de una
clase derivada a uno de la clase base.
// Programa: Herencia Simple: Conversiones entre clases
// Fichero: HER_SIM3.CPP
#include <iostream.h>
class Primera {
protected:
int i, j;
public:
Primera() {i=j=0;}
Primera(int a, int b) {i=a; j=b;}
void Entra_ij(int a, int b) {i=a; j=b;}
void Muestra_ij (void) {cout << "\n" << i << " " << j;}
};
class Segunda: public Primera {
int k;
public:
void Entra_k(int a) {k=a;}
void Muestra_k (void) {cout << " " << k;}
};
void main (void)
{
Primera P(1,2);
Segunda S;
S.Entra_ij(3,4);
S.Entra_k(5);
P.Muestra_ij(); // Saca 1 2
S.Muestra_ij(); // Saca 3 4
S.Muestra_k(); // Saca 5
P=S; // Correcto
P.Muestra_ij(); // Saca 3 4
}
Un punto mucho más interesante es la conversión de un puntero a una clase derivada, en un
puntero a una clase objeto.
Para ver este caso, se va a presentar el siguiente ejemplo. Se tiene la siguiente escala de
empleados, que se va a representar como una estructura de clases derivadas.
50
// Programa: Empleados
// Fichero: EMPLE1.CPP
#include <iostream.h>
#include <string.h>
class Empleado {
protected:
char nombre[30];
public:
Empleado();
Empleado(char *nom);
char *Nombre() const;
};
class aHoras: public Empleado {
protected:
float horas;
float salario;
public:
aHoras ( char *nom );
void FijaSalario ( float s );
void FijaHoras ( float h );
void SacaNombre (void) const;
};
class Gerente: public Empleado {
float salario_semanal;
public:
Gerente ( char *nom );
void FijaSalario (float s);
};
class Vendedor: public aHoras {
float comision;
51
float ventas;
public:
Vendedor (char *nom );
void FijaComision ( float c );
void FijaVentas ( float v );
void SacaComision (void);
};
// Funciones miembro de la clase Empleado
Empleado::Empleado () {
memset ( nombre, ' ', 30);
}
Empleado::Empleado( char * nom) {
strncpy (nombre, nom, 30);
}
char *Empleado::Nombre() const {
return (char *)nombre;
}
// Funciones miembro de la clase aHoras
aHoras::aHoras ( char *nom ):Empleado (nom) {
horas=0.0; salario=0.0;
}
void aHoras::FijaSalario (float s) {
salario=s;
}
void aHoras::FijaHoras (float h) {
horas=h;
}
void aHoras::SacaNombre (void) const {
cout << "\nEl nombre del trabajador es: " << nombre;
}
// Funciones miembro de la clase Gerente
Gerente::Gerente ( char *nom ):Empleado (nom) {
salario_semanal=0.0;
}
void Gerente::FijaSalario (float s) {
salario_semanal=s;
}
// Funciones miembro de la clase Vendedor
Vendedor::Vendedor (char *nom ):aHoras(nom) {
comision=0.0;
ventas=0.0;
52
}
void Vendedor::FijaComision (float c) {
comision=c;
}
void Vendedor::FijaVentas (float v) {
ventas=v;
}
void Vendedor::SacaComision (void) {
cout << "\n" << comision;
}
void main(void)
{
Empleado *ptr_emp;
aHoras a1("Juan Antonio P.");
Gerente g1("Alberto Lamazares");
Vendedor v1("Ana Elena");
ptr_emp=&a1;
cout << "\n" << ptr_emp->Nombre();
ptr_emp=&g1;
cout << "\n" << ptr_emp->Nombre();
ptr_emp=&v1;
cout << "\n" << ptr_emp->Nombre();
}
Se puede utilizar un puntero a Empleado para apuntar directamente a una instancia de aHoras, a
una instancia de Gerente, o a una instancia de Vendedor. Sin embargo, si la definición de la
clase Vendedor hubiera sido:
class Vendedor : aHoras { ...
Para poder con un puntero a Empleado apuntar a una instancia de la clase Vendedor se hubiera
tenido que hacer el siguiente casting.
ptr_emp=(Empleado *)&v1;
Esto es debido a que cuando en la herencia se utiliza el método de acceso private o protected no
se puede convertir de forma implícita un puntero de la clase derivada a un puntero de la clase
base.
53
Cuando se hace referencia a un objeto a través de un puntero, el tipo de puntero determina que
funciones miembro se pueden llamar. Si se utiliza un puntero a una clase base para apuntar a un
objeto de una clase derivada, sólo se podrán utilizar las funciones definidas en la clase base. Por
ejemplo, estudiar el siguiente programa principal.
void main(void)
{
Vendedor v1("Ana Elena"), *v2_ptr;
aHoras *a1_ptr;
v2_ptr=&v1;
a1_ptr=&v1;
a1_ptr->FijaHoras(40.0); // Se llama a aHoras::FijaHoras
v2_ptr->FijaSalario(6.0); // Se llama a aHoras::FijaSalario
// a1_ptr->FijaVentas(1000.0); // Error:no existe aHoras::FijaVentas
v2_ptr->FijaVentas(1000.0); // Se llama a Vendedor::FijaVentas
}
Ambos punteros v2_ptr y a1_ptr apuntan al mismo objeto Vendedor. No se puede llamar a la
función FijaVentas a través de a1_ptr porque la clase aHoras no tiene definida una función que
se llame así.
El caso contrario, que un puntero de una clase derivada apunte a un objeto de la clase base, se
puede hacer mediante un casting apropiado, pero es muy peligroso y no es recomendable
hacerlo, ya que no se está seguro a lo que se apunta. Así el siguiente caso:
void main(void)
{
aHoras a1("Pepe Pérez");
Empleado *ptr = &a1;
Vendedor *v1;
v1=(Vendedor *) ptr; // Lícito pero incorrecto
cout << "\n" << v1->Nombre(); // Saca Pepe Pérez
v1->FijaComision(1.5); // La clase aHoras no tiene
// el miembro FijaComision
v1->SacaComision(); // Saca basura
}
54
Constructores y destructores en herencia.Los constructores y destructores no son heredados por las clases derivadas. Sin embargo, una
instancia de una clase derivada contendrá todos los miembros de la clase base, y éstos deben ser
iniciados. En consecuencia, el constructor de la clase base debe ser llamado por el constructor de
la clase derivada.
Cuando se escribe un constructor para la clase derivada, se debe especificar una forma de inicio
para la base. La forma de hacerlo es utilizar una sintaxis similar a la empleada con los miembros
de inicio para los objetos miembro de una clase. Esto es, se coloca : después de la lista de
argumentos del constructor de la clase derivada, y seguidamente el nombre de la clase base y la
lista de argumentos.
// Programa: Constructores en la herencia
// Fichero: CONSTR.CPP
#include <iostream.h>
class B {
int a, b, c;
public:
B(){a=b=c=0;}
B( int a1, int a2, int a3 )
{
a=a1; b=a2; c=a3;}
void DarValores( int a1, int a2, int a3 )
{
a=a1; b=a2; c=a3;
}
void MostrarEnteros (void)
{
cout << "\n" << a << " " << b << " " << c;
}
};
class C: public B {
float x, y;
public:
C():B(0,0,0) {x=y=0.0;}
C(float a1, float a2, int a3, int a4, int a5) : B(a3, a4, a5)
{
x=a1; y=a2;
55
}
void MostrarFloat(void)
{
cout << "\n" << x << " " << y;
}
};
void main()
{
C b, c(1, 2, 3, 4, 5);
b.MostrarEnteros();
b.MostrarFloat();
c.MostrarEnteros();
c.MostrarFloat();
}
Cuando se declara un objeto de una clase derivada, el compilador ejecuta el constructor de la
clase base en primer lugar, y después ejecuta el constructor de la clase derivada. Si la clase
derivada tuviera objetos miembro, sus constructores se ejecutarían después del constructor de la
clase base, pero antes que el constructor de la clase derivada. Por lo que respecta a los
destructores en la jerarquía de clases, se llaman en orden inverso de la derivación, es decir de la
clase derivada a la clases base. La última clase creada es la que se destruye en primer lugar.
Herencia Múltiple.Una clase puede tener más de una clase base. Esto significa que una clase puede heredar de dos
o más clases. A este fenómeno se le conoce como Herencia Múltiple.
La sintaxis de la herencia múltiple es una extensión de la utilizada para la herencia simple. La
manera de expresar este tipo de herencia es mediante una lista de herencia, que consta de las
clases de las que se hereda separadas por comas. La forma general es:
class < nombre_clase_derivada > : < lista_de_herencia > {
// Cuerpo de la clase
};
Ejemplo: Vamos a ilustrar la herencia múltiple con un sencillo ejemplo.
56
// Programa: Herencia Múltiple
// Fichero: HER_MUL1.CPP
#include <iostream.h>
class Base1 {
protected:
int b1;
public:
void Inicia_B1(int v) { b1=v; }};
class Base2 {
protected:
int b2;
public:
void Inicia_B2(int v) { b2=v; }
int Ret (void) { return b2; }
};
class Der: public Base1, public Base2 {
public:
void print (void) { cout << "\nb1 = " << b1 << "\tb2 = " << Ret(); }
};
void main (void)
{
Der d;
d.Inicia_B2(78);
d.Inicia_B1(34);
d.print(); // Saca 34 y 78
}
Este es un ejemplo muy sencillo en el que una clase, Der, hereda de otras dos clases Base1,
Base2. En este programa no hay complicaciones de ningún tipo, y no es muy interesante.
En la herencia múltiple las reglas de inicio de los miembros son una extensión de los vistos en la
herencia simple. De igual forma, los mecanismos de herencia de los miembros están gobernados
por las declaraciones de private, protected y public en las clases.
El orden en el que se especifican las clases bases en la lista de herencia sólo afecta al orden en
que se van a invocar los constructores, y destructores de las clases base desde los constructores
y destructores de la clase derivada.
Vamos a ver a continuación otro ejemplo de herencia múltiple, en el que las clases tengan
constructores.
57
// Programa: Herencia Múltiple
// Fichero: HER_MUL2.CPP
#include <iostream.h>
class A {
protected:
int a;
public:
A (int valor) {
cout << "\nConstructor de A.";
a=valor;
}
~A () { cout << "\nDestructor de A."; }
};
class B {
protected:
int b;
public:
B (int valor) {
cout << "\nConstructor de B.";
b=valor;
}
~B () { cout << "\nDestructor de B."; }
};
// C hereda de A y de B
class C: public A, public B {
public:
C(int v1, int v2) : B(v2), A(v1) {
cout << "\nConstructor de C.";
}
~C () { cout << "\nDestructor de C."; }
int operar (void) { return a*a+b*b; }
};
void main (void)
{
C obj(4, 5);
cout << "\n" << obj.operar();
};
58
De lo que se deduce que las clases bases se construyen en el orden en que aparecen en la
declaración de C. Una vez que las clases han recibido los valores iniciales, se ejecuta el
constructor de la clase derivada.
Mientras que ninguna de las clases base tenga constructor, o que todas las clases bases tengan
un constructor por defecto, la clase derivada no tiene porque tener constructor. De todas formas
es una buena práctica de programación que todas las clases tengan un constructor para de esta
forma contar con un mecanismo que permita pasar argumentos a los constructores de sus clases
bases.
En cuanto a los destructores, decir que al igual que en la herencia simple, se llaman en orden
inverso a la derivación.
Los mecanismos de herencia múltiple permiten al programador situar la información donde sea
necesario. En contra, la herencia simple puede situar parte de los datos donde no se requieran.
Supóngase que se tiene el siguiente árbol de jerarquía, y sólo se desea que existan datos de otra
clase X en las clases E y F.
Con herencia simple se tendría el siguiente esquema:
59
Con herencia múltiple la solución sería:
La herencia múltiple es más apropiada para definir objetos compuestos o una combinación de
objetos. Mientras que la herencia simple es más útil para definir objetos que son más
especializaciones de objetos generales.
60
TEMA V
FUNCIONES VITUALES Y POLIMORFISMO
Introducción.El Polimorfismo (implementado en C++ con funciones virtuales) es la tercera característica
esencial de un lenguaje orientado a objetos, después de la abstracción de datos y la herencia.
De hecho, nos provee de otra dimensión para la separación entre interfaz y la implementación,
desacoplando el qué del cómo. El Polimorfismo permite mejorar la organización del código y su
legibilidad así como la creación de programas extensibles que pueden "crecer" no sólo durante
el desarrollo del proyecto, si no también cuando se deseen nuevas características.
La encapsulación crea nuevos tipos de datos combinando características y comportamientos. El
control de acceso separa la interfaz de la implementación haciendo privados (private) los
detalles. Estos tipos de organización son fácilmente entendibles por cualquiera que venga de la
programación procedimental. Pero las funciones virtuales tratan de desunir en términos de tipos.
En el Capítulo 14, usted vió como la herencia permitía tratar a un objeto como su propio tipo o
como a su tipo base. Esta habilidad es básica debido a que permite a diferentes tipos (derivados
del mismo tipo base) ser tratados como si fueran un único tipo, y un único trozo de código es
capaz de trabajar indistintamente con todos. Las funciones virtuales permiten a un tipo expresar
sus diferencias con respecto a otro similar si ambos han sido derivados del mismo tipo base.
Esta distinción se consigue modificando las conductas de las funciones a las que se puede
llamar a través de la clase base.
61
En este capítulo aprenderá sobre las funciones virtuales, empezando con ejemplos simples que
le mostrará lo "desvirtual" del programa.
Ligadura.Todo el trabajo se realiza detrás del telón gracias al compilador, que instala los mecanismos
necesarios de la ligadura dinámica cuando se crean funciones virtuales. Debido a que los
programadores se suelen beneficiar de la comprensión del mecanismo de las funciones virtuales
en C++, esta sección mostrará la forma en que el compilador implementa este mecanismo.
La palabra reservada virtual le dice al compilador que no debe realizar ligadura estática. Al
contrario, debe instalar automáticamente todos los mecanismos necesarios para realizar la
ligadura dinámica. Esto significa que si se llama a play() para un objeto Brass a través una
dirección a la clase base Instrument, se usará la función apropiada.
Para que funcione, el compilador típico crea una única tabla (llamada VTABLE) por cada clase
que contenga funciones virtuales. El compilador coloca las direcciones de las funciones virtuales
de esa clase en concreto en la VTABLE. En cada clase con funciones virtuales el compilador
coloca de forma secreta un puntero llamado vpointer (de forma abreviada VPTR), que apunta a la
VTABLE de ese objeto. Cuando se hace una llamada a una función virtual a través de un puntero
a la clase base (es decir, cuando se hace una llamada usando el polimorfismo), el compilador
silenciosamente añade código para buscar el VPTR y así conseguir la dirección de la función en la
VTABLE, con lo que se llama a la función correcta y tiene lugar la ligadura dinámica.
Todo esto - establecer la VTABLE para cada clase, inicializar el VPTR, insertar código para la
llamada a la función virtual - sucede automáticamente sin que haya que preocuparse por ello. Con
las funciones virtuales, se llama a la función apropiada de un objeto, incluso aunque el compilador
no sepa el tipo exacto del objeto.
 Almacenando información de tipo.Se puede ver que no hay almacenada información de tipo de forma explícita en ninguna de las
clases. Pero los ejemplos anteriores, y la simple lógica, dicen que debe existir algún tipo de
información almacenada en los objetos; de otra forma el tipo no podría ser establecido en tiempo
de ejecución. Es verdad, pero la información de tipo está oculta. Para verlo, aquí está un ejemplo
que muestra el tamaño de las clases que usan funciones virtuales comparadas con aquellas que
no las usan:
62
//: C15:Sizes.cpp
// Object sizes with/without virtual functions
#include <iostream>
using namespace std;
class NoVirtual {
int a;
public:
void x() const {}
int i() const { return 1; }
};
class OneVirtual {
int a;
public:
virtual void x() const {}
int i() const { return 1; }
};
class TwoVirtuals {
int a;
public:
virtual void x() const {}
virtual int i() const { return 1; }
};
int main() {
cout << "int: " << sizeof(int) << endl;
cout << "NoVirtual: "
<< sizeof(NoVirtual) << endl;
cout << "void* : " << sizeof(void*) << endl;
cout << "OneVirtual: "
<< sizeof(OneVirtual) << endl;
cout << "TwoVirtuals: "
<< sizeof(TwoVirtuals) << endl;
} ///:~
Sin funciones virtuales el tamaño del objeto es exactamente el que se espera: el tamaño de un
único int. Con una única función virtual en OneVirtual, el tamaño del objeto es el tamaño de
NoVirtual más el tamaño de un puntero a void. Lo que implica que el compilador añade un único
puntero (el VPTR) en la estructura si se tienen una o más funciones virtuales. No hay diferencia de
63
tamaño entre OneVirtual y TwoVirtuals. Esto es porque el VPTR apunta a una tabla con
direcciones de funciones. Se necesita sólo una tabla porque todas las direcciones de las funciones
virtuales están contenidas en esta tabla.
Este ejemplo requiere como mínimo un miembro de datos. Si no hubiera miembros de datos, el
compilador de C++ hubiera forzado a los objetos a ser de tamaño no nulo porque cada objeto
debe tener direcciones distintas (¿se imagina cómo indexar un array de objetos de tamaño nulo?).
Por esto se inserta en el objeto un miembro "falso" ya que de otra forma tendríá un tamaño nulo.
Cuando se inserta la información de tipo gracias a la palabra reservada virtual, ésta ocupa el lugar
del miembro "falso". Intente comentar el int a en todas las clases del ejemplo anterior para
comprobarlo.
Tipos de ligaduras. Ligadura Estática: Se produce antes de la ejecución (durante la compilación).
 Ligadura Dinámica: La ligadura dinámica ocurre en la ejecución.
Funciones virtuales.Para que la ligadura dinámica tenga efecto en una función particular, C++ necesita que se use
la palabra reservada virtual cuando se declara la función en la clase base. La ligadura en
tiempo de ejecución funciona unícamente con las funciones virtual es, y sólo cuando se está
usando una dirección de la clase base donde exista la función virtual, aunque puede ser
definida también en una clase base anterior.
Para crear una función miembro como virtual, simplemente hay que preceder a la declaración
de la función con la palabra reservada virtual. Sólo la declaración necesita la palabra reservada
virtual, y no la definición. Si una función es declarada como virtual, en la clase base, será
entonces virtual en todas las clases derivadas. La redefinición de una función virtual en una
clase derivada se conoce como overriding.
Hay que hacer notar que sólo es necesario declarar la función como virtual en la clase base.
Todas las funciones de las clases derivadas que encajen con la declaración que esté en la
clase base serán llamadas usando el mecanismo virtual. Se puede usar la palabra reservada
virtual en las declaraciones de las clases derivadas (no hace ningún mal), pero es redundante
y puede causar confusión.
Para conseguir el comportamiento deseado de Instrument2.cpp, simplemente hay que añadir
la palabra reservada virtual en la clase base antes de play().
64
//: C15:Instrument3.cpp
// Late binding with the virtual keyword
#include <iostream>
using namespace std;
enum note { middleC, Csharp, Cflat }; // Etc.
class Instrument {
public:
virtual void play(note) const {
cout << "Instrument::play" << endl;
}
};
// Wind objects are Instruments
// because they have the same interface:
class Wind : public Instrument {
public:
// Override interface function:
void play(note) const {
cout << "Wind::play" << endl;
}
};
void tune(Instrument& i) {
65
// ...
i.play(middleC);
}
int main() {
Wind flute;
tune(flute); // Upcasting
} ///:~Instrument3.cpp
Este archivo es idéntico a Instrument2.cpp excepto por la adición de la palabra reservada
virtual y, sin embargo, el comportamiento es significativamente diferente: Ahora la salida es
Wind::play.
 Extensibilidad
Con play() definido como virtual en la clase base, se pueden añadir tantos nuevos tipos como
se quiera sin cambiar la función play(). En un programa orientado a objetos bien diseñado, la
mayoría de las funciones seguirán el modelo de play() y se comunicarán únicamente a través
de la interfaz de la clase base. Las funciones que usen la interfaz de la clase base no
necesitarán ser cambiadas para soportar a las nuevas clases.
Aquí está el ejemplo de los instrumentos con más funciones virtuales y un mayor número de
nuevas clases, las cuales trabajan de manera correcta con la antigua (sin modificaciones)
función play():
//: C15:Instrument4.cpp
// Extensibility in OOP
#include <iostream>
using namespace std;
enum note { middleC, Csharp, Cflat }; // Etc.
66
class Instrument {
public:
virtual void play(note) const {
cout << "Instrument::play" << endl;
}
virtual char* what() const {
return "Instrument";
}
// Assume this will modify the object:
virtual void adjust(int) {}
};
class Wind : public Instrument {
public:
void play(note) const {
cout << "Wind::play" << endl;
}
char* what() const { return "Wind"; }
void adjust(int) {}
};
class Percussion : public Instrument {
public:
void play(note) const {
cout << "Percussion::play" << endl;
}
67
char* what() const { return "Percussion"; }
void adjust(int) {}
};
class Stringed : public Instrument {
public:
void play(note) const {
cout << "Stringed::play" << endl;
}
char* what() const { return "Stringed"; }
void adjust(int) {}
};
class Brass : public Wind {
public:
void play(note) const {
cout << "Brass::play" << endl;
}
char* what() const { return "Brass"; }
};
class Woodwind : public Wind {
public:
void play(note) const {
cout << "Woodwind::play" << endl;
}
char* what() const { return "Woodwind"; }
68
};
// Identical function from before:
void tune(Instrument& i) {
// ...
i.play(middleC);
}
// New function:
void f(Instrument& i) { i.adjust(1); }
// Upcasting during array initialization:
Instrument* A[] = {
new Wind,
new Percussion,
new Stringed,
new Brass,
};
int main() {
Wind flute;
Percussion drum;
Stringed violin;
Brass flugelhorn;
Woodwind recorder;
tune(flute);
69
tune(drum);
tune(violin);
tune(flugelhorn);
tune(recorder)
f(flugelhorn);
} ///:~Instrument4.cpp
Se puede ver que se ha añadido otro nivel de herencia debajo de Wind, pero el mecanismo
virtual funciona correctamente sin importar cuantos niveles haya. La función adjust() no está
redefinida (override) por Brass y Woodwind. Cuando esto ocurre, se usa la definición más
"cercana" en la jerarquía de herencia - el compilador garantiza que exista alguna definición
para una función virtual, por lo que nunca acabará en una llamada que no esté enlazada con el
cuerpo de una función (lo cual sería desatroso).
El array A[] contiene punteros a la clase base Instrument, lo que implica que durante el
proceso de inicialización del array habrá upcasting. Este array y la función f() serán usados en
posteriores discusiones.
En la llamada a tune(), el upcasting se realiza en cada tipo de objeto, haciendo que se obtenga
siempre el comportamiento deseado. Se puede describir como "enviar un mensaje a un objeto y
dejar al objeto que se preocupe sobre qué hacer con él". La función virtual es la lente a usar
cuando se está analizando un proyecto: ¿Dónde deben estar las clases base y cómo se desea
extender el programa? Sin embargo, incluso si no se descubre la interfaz apropiada para la
clase base y las funciones virtuales durante la creación del programa, a menudo se descubrirán
más tarde, incluso mucho más tarde cuando se desee ampliar o se vaya a hacer funciones de
mantenimiento en el programa. Esto no implica un error de análisis o de diseño; simplemente
significa que no se conocía o no se podía conocer toda la información al principio. Debido a la
fuerte modularización de C++, no es mucho problema que esto suceda porque los cambios que
se hagan en una parte del sistema no tienden a propagarse a otras partes como sucede en C.
70
Polimorfismo.Otro concepto interesante, con Importantes aportaciones en áreas tales como la flexibilidad o la
legibilidad del software, es el de polimorfismo. Tras este termino, un tanto oscuro, subyace una
idea bastante simple. En su más amplia expresión, el polimorfismo puede definirse como:
"el mecanismo que permite definir e Invocar funciones idénticas en denominación e interfaz, pero
con implementaron diferente".
Esta definición introduce un aspecto muy importante del polimorfismo: la asociación, o vinculo,
entre cada llamada a una de estas funciones polimorfismo y la implementación concreta
finalmente invocada. Cuando este vinculo puede establecerse en tiempo de compilación, se suele
hablar de vinculación estatica (static binding). Por contra, cuando la implementación a emplear,
puede determinarse en tiempo de ejecución, el termino empleado es el de vinculación dinámica
(dynamic binding).
En C++, por ejemplo, la vinculación dinámica de las llamadas a funciones polimórficas (en C++
reciben el calificativo de funciones virtuales) se consigue en base a la posibilidad que ofrece este
lenguaje de utilizar un puntero a objetos de una clase como puntero a objetos de cualquiera de las
clases descendientes de la anterior. Así, cuando la llamada a una función virtual, definida en una
clase y en una o varias de sus descendientes, se realiza sobre un objeto que viene referenciado
mediante un puntero a la clase padre, el compilador es incapaz de determinar que implementación
debe asociar a la llamada, ya que desconoce cual será la clase del objeto en el momento de su
ejecución. Dicha determinación debe quedar aplazada, por tanto, hasta ese instante.
Como se puede observar, el concepto de polimorfismo en C++, y en general en casi todos los
lenguajes de programación basados en el paradigma de objeto, esta estrechamente ligado al
concepto de herencia, dado que las funciones polimórficas sólo pueden definirse entre clases que
guardan entre sí una relación de parentesco (clases con un antecesor común).
Aunque el concepto de polimorfismo es una de las principales innovaciones del desarrollo
orientado a objetos, posee antecedentes históricos en otros mecanismos más sencillos, como son
la conversión forzada (casting) y la sobrecarga de identificadores, ideados con el fin de introducir
un cierto grado de flexibilidad en el manejo de tipos de datos heterogéneos.
En el siguiente apartado se desarrolla un pequeño ejemplo de aplicación de ambos conceptos, en
el que quedan claramente de manifiesto las ventajas potenciales de una correcta utilización de los
mismos. Para su implementación se ha elegido el lenguaje C ++, fundamentalmente por dos
razones:
#include <iostream.h>
void show(int val)
{
71
cout <<" Es un entero :"<< val << '\n';
}
void show(char *val)
{
cout <<"Es un carácter: "<< val << '\n';
}
main()
{
show (42);
show ("A");
show (452.2);
}
TEMA VI
PLANTILLAS Y TRATAMIENTO DE EXCEPCIONES
Introducción.La herencia y la composición proporcionan una forma de retilizar código objeto. Las plantillas de
C++ proporcionan una manera de reutilizar el código fuente.
Aunque las plantillas (o templates) son una herramienta de programación de propósito general,
cuando fueron introducidos en el lenguaje, parecían oponerse al uso de las jerarquías de clases
contenedoras basadas en objetos (demostrado al final del Capítulo 15). Además, los
contenedores y algoritmos del C++ Standard están construidos exclusivamente con plantillas y
son relativamente fáciles de usar por el programador.
Este capítulo no sólo muestra los fundamentos de los templates, también es una introducción a
los contenedores, que son componentes fundamentales de la programación orientada a objetos
lo cual se evidencia a través de los contenedores de la librería estándar de C++. Se verá que
este libro ha estado usando ejemplos contenedores - Stash y Stack- para hacer más sencillo el
concepto de los contenedores; en este capítulo se sumará el concepto del iterator. Aunque los
contenedores son el ejemplo ideal para usarlos con las plantillas, en el Volumen 2 (que tiene un
capítulo con plantillas avanzadas) se aprenderá que también hay otros usos para los templates
Plantillas.-
Ahora surge un nuevo problema. Tenemos un IntStack, que maneja enteros. Pero queremos
una pila que maneje formas, o flotas de aviones, o plantas o cualquier otra cosa. Reinventar el
código fuente cada vez no parece una aproximación muy inteligente con un lenguaje que
propugna la reutilización. Debe haber un camino mejor.
72
Hay tres técnicas para reutilizar código en esta situación: el modo de C, presentado aquí como
contraste; la aproximación de Smalltalk, que afectó de forma significativa a C++, y la
aproximación de C++: los templates.
 La solución de C. Por supuesto hay que escapar de la aproximación de C porque es
desordenada y provoca errores, al mismo tiempo que no es nada elegante. En esta
aproximación, se copia el código de una Stack y se hacen modificaciones a mano,
introduciendo nuevos errores en el proceso. Esta no es una técnica muy productiva.
 La solución de Smalltalk. Smalltalk (y Java siguiendo su ejemplo) optó por una solución
simple y directa: Se quiere reutilizar código, pues utilicese la herencia. Para
implementarlo, cada clase contenedora maneja elementos de una clase base genérica
llamada Object (similar al ejemplo del final del capítulo 15). Pero debido a que la librería
de Smalltalk es fundamental, no se puede crear una clase desde la nada. En su lugar,
siempre hay que heredar de una clase existente. Se encuentra una clase lo más cercana
posible a lo que se desea, se hereda de ella, y se hacen un par de cambios. Obviamente,
esto es un beneficio porque minimiza el trabajo (y explica porque se pierde un montón de
tiempo aprendiendo la librería antes de ser un programador efectivo en Smalltalk).
Pero también significa que todas las clases de Smalltalk acaban siendo parte de un único árbol
de herencia. Hay que heredar de una rama de este árbol cuando se está creando una nueva
clase. La mayoría del árbol ya esta allí (es la librería de clases de Smalltalk), y la raiz del árbol es
una clase llamada Object - la misma clase que los contenedores de Smalltalk manejan.
Es un truco ingenioso porque significa que cada clase en la jerarquía de herencia de Smalltalk (y
Java) se deriva de Object, por lo que cualquier clase puede ser almacenada en cualquier
contenedor (incluyendo a los propios contenedores). Este tipo de jerarquía de árbol única basada
en un tipo genérico fundamental (a menudo llamado Object, como también es el caso en Java)
es conocido como "jerarquía basada en objectos". Se puede haber oido este témino y asumido
que es un nuevo concepto fundamental de la POO, como el polimorfismo. Sin embargo,
simplemente se refiere a la raíz de la jerarquía como Object (o algún témino similar) y a
contenedores que almacenan Objects.
Debido a que la librería de clases de Smalltalk tenía mucha más experiencia e historia detrás de
la que tenía C++, y porque los compiladores de C++ originales no tenían librerías de clases
contenedoras, parecía una buena idea duplicar la librería de Smalltalk en C++. Esto se hizo
como experimento con una de las primeras implementaciónes de C++, y como representaba un
73
significativo ahorro de código mucha gente empezo a usarlo. En el proceso de intentar usar las
clases contenedoras, descubrieron un problema.
El problema es que en Smalltalk (y en la mayoría de los lenguajes de POO que yo conozco),
todas las clases derivan automáticamente de la jerarquía única, pero esto no es cierto en C++.
Se puede tener una magnifica jerarquía basada en objetos con sus clases contenedoras, pero
entonces se compra un conjunto de clases de figuras, o de aviones de otro vendedor que no usa
esa jerarquía. (Esto se debe a que usar una jerarquía supone sobrecarga, rechazada por los
programadores de C). ¿Cómo se inserta un árbol de clases independientes en nuestra jerarquía?
El problema se parece a lo siguiente:
 Contenedores.Debido a que C++ suporta múltiples jerarquías independientes, la jerarquía basada en objetos
de Smalltalk no funciona tan bien.
La solución parace obvia. Si se pueden tener múltiples jerarquías de herencia, entonces hay
que ser capaces de heredar de más de una clase: La herencia múltiple resuelve el problema.
Por lo que se puede hacer lo siguiente:
 Herencia múltiple.-
74
Ahora OShape tiene las características y el comportamiento de Shape, pero como también está
derivado de Object puede ser insertado en el contenedor. La herencia extra dada a OCircle,
OSquare, etc. es necesaria para que esas clases puedan hacer upcast hacia OShape y puedan
mantener el comportamiento correcto. Se puede ver como las cosas se están volviendo confusas
rápidamente.
Los vendedores de compiladores inventaron e incluyeron sus propias jerarquías y clases
contenedoras, muchas de las cuales han sido reemplazadas desde entonces por versiones de
templates.
 La solución de la plantilla.Aunque una jerarquía basada en objetos con herencia múltiple es conceptualmente correcta, se
vuelve difícil de usar se demostró lo que se consideraba una alternativa preferible a la jerarquía
basada en objetos. Clases contenedoras que fueran creadas como grandes macros del
preprocesador con argumentos que pudieran ser sustituidos con el tipo deseado. Cuando se
quiera crear un contenedor que maneje un tipo en concreto, se hacen un par de llamadas a
macros.
Desafortunadamente, esta aproximación era confusa para toda la literatura existente de
Smalltalk y para la experiencia de programación, y era un poco inmanejable. Básicamente,
nadie la entendía.
Mientras tanto, Stroustrup y el equipo de C++ de los Laboratorios Bell habían modificado su
aproximación de las macros, simplificándola y moviéndola del dominio del preprocesador al
compilador. Este nuevo dispositivo de sustitución de código se conoce como template
(plantilla), y representa un modo completamente diferente de reutilizar el código. En vez de
reutilizar código objeto, como en la herencia y en la composición, un template reutiliza código
fuente. El contenedor no maneja una clase base genérica llamada Object, si no que gestiona
un parámetro no especificado. Cuando se usa un template, el parámetro es sustituido por el
compilador, parecido a la antigua aproximación de las macros, pero más claro y fácil de usar.
hora, en vez de preocuparse por la herencia o la composición cuando se quiera usar una clase
contenedora, se usa la versión en plantilla del contenedor y se crea una versión específica para
el problema, como lo siguiente:
75
Figura: Contenedor de objetos
El compilador hace el trabajo por nosotros, y se obtiene el contenedor necesario para hacer el
trabajo, en vez de una jerarquía de herencia inmanejable. En C++, el template implementa el
concepto de tipo parametrizado. Otro beneficio de la aproximación de las plantillas es que el
programador novato que no tenga familiaridad o esté incómodo con la herencia puede usar las
clases contenedoras de manera adecuada (como se ha estado haciendo a lo largo del libro con
el vector).
Concepto de excepciones.Las excepciones son situaciones anómalas que requieren un tratamiento especial.
¡¡No tienen por qué ser errores!!
Si se consigue dominar su programación, la calidad de las aplicaciones que se desarrollen
aumentará considerablemente.
El funcionamiento general del mecanismo de lanzamiento y tratamiento de excepciones es el
siguiente:
 Existe un método que invoca la ejecución de otro.
 Este método más interno se encuentra en una situación que puede considerarse como
excepcional. Por lo tanto lanza una excepción.
 En este momento termina la ejecución del método más interno y se retorna
inmediatamente al método llamador.
 El método llamador debe capturar la excepción y la trata. Parte del tratamiento de la
excepción puede ser volver a lanzarla al método que invocó a este.
La correcta programación de excepciones significa diseñar los algoritmos pensando únicamente
en la forma habitual en la que deben ejecutarse, manejando las situaciones extraordinarias a
76
parte. De esta manera se consigue un diseño mucho más estructurado, legible, robusto y fácil de
mantener.
Ejemplo.Cuando solicitamos memoria al sistema, lo habitual es que exista suficiente memoria disponible y
no haya ningún problema. Pero si queremos realizar una aplicación robusta deberemos de tener
en cuenta la eventualidad de que dicha memoria no se conceda, lo cual complica enormemente
un sencillo algoritmo.
Veamos una función escrita en C que simplemente intenta asignar memoria dinámica para tres
enteros:
void SinExcepciones (void)
{
int *p1, *p2, *p3;
p1 = (int*) malloc(sizeof(int));
if (p1 == NULL) {
printf("No hay suficiente memoria");
abort();
}
p2 = (int*) malloc(sizeof(int));
if (p2 == NULL) {
printf("No hay suficiente memoria");
abort();
}
p3 = (int*) malloc(sizeof(int));
if (p3 == NULL) {
printf("No hay suficiente memoria");
abort();
77
}
}
Si programamos en C++ y hacemos uso de las excepciones:
void ConExcepciones (void) {
int *p1, *p2, *p3;
try {
p1 = new int; // Comportamiento normal.
p2 = new int;
p3 = new int;
}
catch(...){ // Comportamiento excepcional.
printf("No hay suficiente memoria");
abort();
}
}
El ANSI C++ especifica que si una instrucción new falla (no hay memoria disponible) debe lanzar
la excepción bad_alloc (definida en el fichero de cabecera new). Las instrucciones que pueden
provocar el lanzamiento de una excepción (la "zona crítica") se encierran en un bloque try. Si
alguna de las instrucciones del bloque try provocara una excepción el flujo del programa se dirige
al final de ese bloque, buscando un bloque catch que capture la excepción (si no lo encontrara, el
programa terminaría -ver sección 6.4-). En este caso, la instrucción catch(...) captura cualquier
excepción, y en particular, la excepción bad_alloc. El tratamiento que se efectúa es, en este caso,
mostrar un mensaje de error y terminar la ejecución del programa.
Lanzamiento de excepciones.El lanzamiento de una excepción se realiza llamando a la función throw(). Cuando se lanza una
excepción, en realidad lo que se hace es crear un objeto de la clase que se le indique a throw(), y
precisamente será dicho objeto la excepción en sí.
Suele ser muy útil crear clases de excepciones propias para controlar las situaciones anómalas de
nuestra aplicación. Por ejemplo,
78
En ObjGraf.h:
//*************************************************/
// Definicion de la clase EFueraRango
// Clase de excepcion por entrar en "fuera de rango":
// salirse de los limites del PaintBox al calcular
// las nuevas coordenadas de dibujo de la pelota.
//*************************************************/
class EFueraRango {};
Con lo que podríamos lanzar una excepción de la siguiente manera:
throw EfueraRango();
Nota: Aunque las excepciones sean clases, en C++ Builder existe la convención de que su
nombre empiece por la letra E, y no por T como el resto de las clases.
Aunque en el ejemplo anterior la clase creada para la excepción, no tiene ningún miembro
(propiedades o métodos), se le pueden añadir. Éstos servirán para poder incorporar información
en el objeto excepción, acerca de la situación en la que se produjo la excepción, que podrá ser
utilizada
por
la
sección
de
código
que
lo
trate.
Por ejemplo, si tuviéramos un método que lanzara una excepción por falta de memoria, podría ser
interesante que incorporara una propiedad que indicara cual era el máximo de memoria disponible
cuando se lanzó la excepción.
 Especificación de excepciones.C++ cuenta con una característica denominada especificación de excepciones, que sirve para
enumerar, en una declaración, las excepciones que puede lanzar un método. Si queremos que un
método pueda lanzar un determinado tipo de excepción deberemos especificarlo de esta manera.
En ObjGraf.h, en la clase TObjGraf:
//*************************************************/
// Definicion de la clase base TObjGraf
//*************************************************/
79
class TObjGraf {
private:
int FX;
int FY;
void SetX (int _X) throw (EFueraRango); // Si hay problemas,crean un
void SetY (int _Y) throw (EFueraRango); // objeto de clase EFueraRango
virtual int GetAncho (void) = 0; // Metodo virtual puro
virtual int GetAlto (void) = 0; // Metodo virtual puro
...
En ObjGraf.cpp:
// Funciones de escritura de las propiedades virtuales X e Y
void TObjGraf :: SetX (int _X) throw (EFueraRango)
{
if (_X < 0) { // Coordenada negativa
FX = 0; // Ajustar al margen izquierdo
throw EFueraRango(); // Lanzar excepcion
}
else
if (_X > (PaintBox->Width - Ancho)) { // Demasiado alta
FX = PaintBox->Width - Ancho; // Ajustar al margen derecho
throw EFueraRango(); // Lanzar excepcion
}
else
FX = _X; // Correcto: escribir sin modificar
}
void TObjGraf :: SetY (int _Y) throw (EFueraRango)
{
if (_Y < 0) { // Coordenada negativa
FY = 0; // Ajustar al margen superior
throw EFueraRango(); // Lanzar excepcion
}
else
if (_Y > (PaintBox->Height - Alto)) { // Demasiado alta
FY = PaintBox->Height - Alto; // Ajustar al margen inferior
throw EFueraRango(); // Lanzar excepcion
80
}
else
FY = _Y; // Correcto: escribir sin modificar
}

Captura de excepciones.-
El bloque susceptible de producir alguna excepción ("zona crítica") se encierra en un bloque try, la
captura (discriminación de la excepción que se ha producido) se efectúa en una instrucción catch
y su procesamiento se realiza a continuación, en el bloque catch.
try {
<bloque de instrucciones críticas>
}
catch (<tipo excepción1> <variable1>) {
<manejador 1>
}
catch (<tipo excepción2> <variable2>) {
...
}
Podemos especificar tantos bloques catch para un bloque try como deseemos, en el momento que
ocurra una excepción se ejecutará el bloque catch cuya clase concuerde con la de la excepción.
Si se especifica catch (...) se capturará cualquier excepción.
La excepción no tiene porqué lanzarse explícitamente en el bloque try sino que puede ser
consecuencia de la ejecución de una función que se ha llamado dentro de ese bloque.
En ObjGraf.cpp:
void TPelota :: Mover (void)
{
Borrar ();
try {
X += FDirX * Velocidad;
}
catch (EFueraRango) {
81
FDirX = -FDirX;
};
try {
Y += FDirY * Velocidad;
}
catch (EFueraRango) {
FDirY = -FDirY;
};
Mostrar ();
}
En este ejemplo, la instrucción "crítica":
X += FDirX * Velocidad;
se traduce a:
X = X + FDirX * Velocidad;
y como X es una propiedad virtual, en realidad se trata de hacer:
SetX (FX + FDirX * Velocidad);
donde se observa que es el método SetX() el que puede provocar el lanzamiento de la excepción.
En Ppal.cpp:
1.
Dejar únicamente la siguiente declaración global:
2.
3.
4.
TPelota *Pelota;
Modificar las funciones FormCreate() y FormDestroy() para que queden:
//----------------------------------------------------------
5.
6.
void __fastcall TPpalFrm::FormCreate(TObject *Sender)
7.
{
8.
Pelota = new TPelota (PaintBox, clYellow, 120, 70, 25);
9.
}
10.
//----------------------------------------------------------
11.
12.
void __fastcall TPpalFrm::FormDestroy(TObject *Sender)
13.
{
14.
15.
delete Pelota;
}
16.
Eliminar el manejador del evento OnPaint del componente PaintBox (borrando su cuerpo solamente).
17.
Añadir a PpalFrm un TTimer (Carpeta System) y fijar Interval=100. En el evento OnTimer poner:
18.
82
//----------------------------------------------------------
19.
20.
void __fastcall TPpalFrm::Timer1Timer(TObject *Sender)
21.
{
22.
Pelota->Mover();
23.
}
24.
//---------------------------------------------------------
LECTURAS COPLEMENTARIAS
Disciplinas relacionadas con POA
Por último, hay muchas disciplinas y campos de investigación relacionados de alguna manera con
la orientación a aspectos, algunos persiguen los mismos objetivos y algunos otros. En este
apartado se hará mención a algunos de ellos, de manera que se tenga una visión más amplia de
por dónde se mueve la investigación en esta disciplina.
 Reflexión y protocolos de meta objetos.Un sistema reflectivo proporciona un lenguaje base y uno o más metalenguajes que proporcionan
control sobre la semántica del lenguaje base y la implementación. Los metalenguajes
proporcionan vistas de la computación que ningún componente del lenguaje base puede percibir.
Los metalenguajes son de más bajo nivel que los lenguajes de aspectos en los que los puntos de
enlace son equivalentes a los “ganchos” de la programación reflectiva. Estos sistemas se pueden
utilizar como herramienta para la programación orientada a aspectos.
 Transformación de programas.El objetivo que persigue la transformación de programas es similar al de la orientación a aspectos.
Busca el poder escribir programas correctos en un lenguaje demás alto nivel, y luego,
transformarlos de forma mecánica en otros que tengan un comportamiento idéntico, pero cuya
eficiencia sea mucho mejor. Con este estilo de programación algunas propiedades de las que el
programador quiere implementar se escriben en un programa inicial. Otras se añaden gracias a
que el programa inicial pasa a través de varios programas de transformación. Esta separación es
similar a la que se produce entre los componentes y los aspectos.
 Programación subjetiva.La programación subjetiva y la orientada a aspectos son muy parecidas, pero no son exactamente
lo mismo. La programación subjetiva soporta la combinación automática de métodos para un
83
mensaje dado a partir de diferentes sujetos. Estos métodos serían equivalentes a los
componentes tal y como se perciben desde la POA .Hay una diferencia clave entre ambas
disciplinas, y es que mientras que los aspectos de la POA tienden a ser propiedades que afectan
al rendimiento o la semántica de los componentes, los sujetos de la programación subjetiva son
características adicionales que se añaden a otros sujetos.
BIBLIOGRAFIA
AUTOR
OBRA
LUGAR
EDITORIAL
DE
EDICIÓN
AÑO
JOYANES AGUILAR,
LUIS
“PROGRAMACION
ORIENTADA A OBJETOS”
MADRID
MGGRAWHILL
1998
JOYANES AGUILAR,
LUIS
“FUNDAMENTOS DE LA
PROGRAMACION “
MADRID
MGGRAWHILL
2003
BOOCH, GRADY
“DISENO ORIENTADO A
OBJETOS CON
APLICACIONES”
MADRID
MGGRAWHILL
1991
NEW
YORK
ADDISONWESLEY
1998
STAUGUARD,ANDREW “TECNICAS
ESTRUCTURADAS Y
ORIENTADAS A
OBJETOS:UNA
INTRODUCCION
UTILIZANDO C++”
SCHILDT,HERBERT
“APLIQUE TURBO C++”
MADRID
MGGRAWHILL
1991
DEITEL Y DEITEL
“COMO PROGRAMAR EN
JAVA”
MADRID
MGGRAWHILL
2005
84
Descargar