Sei sulla pagina 1di 29

Programación Orientada a Objetos

Historia:

La programación de computadoras es una actividad humana que se ha desarrollado casi enteramente durante la
segunda mitad del siglo XX. Por lo tanto podemos suponer que aún está en sus orígenes y que el futuro traerá todavía
grandes adelantos técnicos y teóricos que mejorarán sus resultados. En su corta historia, la programación ha sufrido
importantes cambios, diríamos “casi revoluciones”. Los primeros avances metodológicamente ordenados, fueron
protagonizados principalmente por Wirth, Dijstra y de forma menos teórica pero quizás con más impacto por Kernighan y
Ritchie. Es lo que se denominó la programación estructurada. Los primeros lenguajes de programación eran
simplemente instrucciones que se le podían dar a un autómata como una computadora, para que realizara ciertas
operaciones. Así un programa no era sino una lista de instrucciones encaminadas a realizar algún cálculo.
A medida que las computadoras fueron haciéndose más sofisticadas, sus lenguajes propios o lenguajes de máquina
iban cambiando y surgió la necesidad de crear unos lenguajes intermedios que cualquier usuario pudiera aprender y
que no dependieran de la máquina concreta en la que se iban a ejecutar los programas. Así surgieron varios lenguajes
que se hicieron famosos y también surgieron los primeros compiladores. Un compilador es un programa que traduce las
instrucciones de un lenguaje más o menos humano, a las de una máquina.
La aparición de estos lenguajes intermedios y sus compiladores marca el comienzo de la programación como una nueva
ciencia. El tremendo éxito que las computadoras tuvieron a lo largo de los años 60 fue llevando a la creación de
programas cada vez más complejos que llegaban a tener miles de líneas de código.
Hacer correcciones a este tipo de programas y agregarles mejoras se fue convirtiendo en una labor muy ardua. Ante
este problema que amenazaba con convertir las computadoras en máquinas estériles, surgió un grupo de científicos de
la computación, de los cuales Dijstra y Wirth son dos de los más destacados. Estos propusieron una serie de ideas que
llevaron a la creación de ese nuevo concepto indicado al inicio: la programación estructurada.
Otros científicos experimentaron ideas similares creando diversos lenguajes de programación orientada a objetos como
Smalltalk. En ellos se experimentó con otras ideas útiles como la definición de subclases que heredan las propiedades
de su superclase o sea de la clase de la que se derivan, pero agregando variables y funciones nuevas. También surgió
una idea que ayudaría a evitar los difíciles problemas que surgían en el manejo de la memoria dinámica: los
constructores y destructores de objetos. Actualmente la Tecnología Orientada a Objetos (TOO) no solo se aplica a los
lenguajes de programación, sino que también se ha propagado a los métodos de análisis y diseño y a otras áreas, tales
como las bases de datos y/o las comunicaciones. Por lo tanto, para hacer desarrollo de sistemas de software basados
en la TOO, hay que entender bien todos los conceptos del modelo de objetos que está detrás de ella y sus
antecedentes históricos. Uno de los defectos de la programación imperativa es que las variables globales pueden ser
utilizadas y modificar sus contenidos, desde cualquier punto del programa.
Los programas que carecen de disciplina para acceder a variables globales tienden a ser inmanejables. La razón es que
los módulos que acceden a estas variables no se pueden comprender completamente, de forma independiente, de
todos aquellos otros módulos que también acceden a las mismas variables globales, es decir, todo está relacionado con
todo.
Este problema fue detectado alrededor de 1970 por David L. Parnas, quien propuso la norma de ocultar información
como solución. Su idea era encapsular cada una de las variables globales del programa en un módulo junto con sus
operaciones asociadas, sólo mediante las cuales se podía tener acceso a estas variables. El resto de los módulos
podrían acceder a las variables sólo de forma indirecta mediante las operaciones diseñadas a tal efecto. En la
actualidad denominamos objetos a este tipode módulos.
El primer lenguaje que introdujo los conceptos de orientación a objetos fue SIMULA 67 creado en Noruega, por un grupo
de investigadores dirigido por O. J. Dahl y K. Nygaard, con el fin de realizar simulaciones discretas de sistemas reales.
En estos tiempos no existían lenguajes de programación que se ajustaran a sus necesidades, así que se basaron en el
lenguaje ALGOL 60 y lo extendieron con conceptos de objetos, clases, herencia, el polimorfismo por inclusión (que se
obtiene introduciendo la herencia de clases) y procedimientos virtuales.
El lenguaje fue utilizado sobre todo en Europa y no tuvo mucho impacto comercial, sin embargo los conceptos que se
definieron en él, se volvieron sumamente importantes para el futuro del desarrollo de software.
La programación estructurada propone dos ideas básicas: no repetir código y proteger las variables que una parte del
programa usa, de que sean modificadas accidentalmente por otras partes del programa. Una sola repetición de código
es fuente probable de errores y dificulta el mantenimiento de un programa. Para no repetir código hay que escribir
funciones o procedimientos que se encarguen de realizar siempre lo que las diferentes repeticiones realizarían. Para
proteger las variables hay que desarrollar lenguajes que permitan definir variables locales, es decir, que sólo
pueden ser utilizadas dentro de una función o procedimiento. De esta manera ninguna otra función del programa puede
cambiarla. Como consecuencia de estas ideas disminuye considerablemente el tamaño de los programas y éstos se
hacen más confiables y más fáciles de corregir o mejorar. Para facilitar la programación estructurada, aparecen nuevos
lenguajes, notoriamente el Pascal y el C, son los dos lenguajes de programación estructurada más conocidos.
Ventajas e inconvenientes de la orientación a objetos
Entre las ventajas más importantes podemos destacar:
_ Favorece la comunicación entre analistas, diseñadores, programadores y
usuarios finales al utilizar todos los mismos modelos conceptuales.
_ Esto se traduce en un aumento de la productividad, ya que la comunicación es
uno de los puntos críticos en las primeras fases del proyecto.
_ Se facilita la representación de estructuras complejas sin necesidad de
adaptarnos a normas y modelos, ya que lo que manejamos son objetos del
mundo real, lo que facilita la tarea del analista.
_ La semántica de estas técnicas es más rica (al ser más natural); al usuario final
le es más fácil comprender lo que el analista representa en sus modelos (ya
que representa los objetos que lo rodean habitualmente)
_ Favorece la modularidad, la reusabilidad y el mantenimiento del software.
_ Estas técnicas son más resistentes al cambio que las tradicionales técnicas de
análisis orientadas a flujos de datos.
Algunas de sus desventajas:
_ Hay que ser muy cuidadosos en la creación de los objetos, ya que de ello
dependerá el éxito de nuestro proyecto. Un error en estas primeras definiciones
podría resultar catastrófico. Precisamente el secreto de esta técnica está en la
correcta definición inicial de los objetos.
_ Los estándares en este tipo de técnicas están en continua evolución, lo que
exige una actualización permanente.
_ Los analistas, diseñadores y desarrolladores del proyecto deben conocer las
reglas del juego y poseer suficiente experiencia en programación.

Información
La Información es una colección de datos que entra, fluye, se procesa y sale de un
Sistema.
El dato es una representación de la información con la estructura más adecuada
para su tratamiento o proceso. Sistemas de información.
Un Sistema es un conjunto de objetos ordenadamente relacionados entre sí, con
arreglo a unas reglas que cooperan para aportar los procesos necesarios para el
cumplimento de una función o finalidad determinada.
Un Sistema de Información se caracteriza porque sus procesos son la adquisición,
manipulación, uso, producción, almacenamiento y distribución de información.
Profundizando el concepto de Objeto.
Las personas tienen una idea clara de lo que es un objeto: conceptos adquiridos
que nos permiten sentir y razonar acerca de las cosas del mundo. Un objeto podría ser
real o abstracto, por ejemplo una organización, una factura, una figura en un
graficador, una pantalla de usuario, un avión, un vuelo de avión, etc.
En el análisis y diseño orientados a objetos (OO), interesa el comportamiento del
objeto. Si se construye software, los módulos de software OO se basan en los tipos de
objetos.
El software que implanta el objeto contiene estructuras de datos y operaciones que
expresan dicho comportamiento. Las operaciones se codifican como métodos.
Entonces, dentro del software orientado a objeto, un objeto es cualquier cosa, real
o abstracta, acerca de la cual almacenamos datos y los métodos que controlan dichos
datos.
Un objeto puede estar compuesto por otros objetos. Estos últimos a su vez
también pueden estar compuestos por otros objetos. Esta intrincada estructura es la
que permite construir objetos muy complejos.
Tipo de objeto.
Los conceptos que poseemos se aplican a tipos determinados de objetos. Por
ejemplo, “empleado” se aplica a los objetos que son personas empleadas por alguna
organización. Algunas instancias de empleado podrían ser Juan Pérez, José Martínez,
etc.
En el análisis orientado a objetos, estos conceptos se llaman tipos de objetos; las
instancias se llaman objetos.
Así, un tipo de objeto es una categoría de objeto, mientras que un objeto es una
instancia de un tipo de objeto.
En el mundo de las bases de datos existen los tipos de entidad, como cliente o
empleado. Existen muchas instancias de cada tipo de entidad (como Juan Pérez o
José Martínez para el tipo de entidad empleado). Del mismo modo, en OO se define
tipos de objetos e instancias de tipo de objeto. Sin embargo, el término objeto tiene diferencias
fundamentales con el término
entidad, ya que la entidad sólo se refiere a los datos, mientras que objeto se refiere a
los datos y a los métodos mediante los cuales se controlan a los propios datos.
En OO, la estructura de datos y los métodos de cada tipo de objeto se manejan
juntos. No se puede tener acceso o control de la estructura de datos excepto mediante
los métodos que forman parte del tipo de objeto.
Estructura de un objeto.
Un objeto puede considerarse como una especie de cápsula dividida en tres
partes:
_ Relaciones
_ Propiedades
_ Métodos
Cada uno de estos componentes desempeña roles totalmente independientes:
_ Las relaciones permiten que el objeto se inserte en la organización y están
formadas esencialmente por punteros a otros objetos.
_ Las propiedades distinguen un objeto determinado de los restantes que
forman parte de la misma organización y tiene valores que dependen de la
propiedad de que se trate. Las propiedades de un objeto pueden ser
heredadas a sus descendientes en la organización.
_ Los métodos son las operaciones que pueden realizarse sobre el objeto,
que normalmente estarán incorporados en forma de programas (código)
que el objeto es capaz de ejecutar y que también pone a disposición de sus
descendientes a través de la herencia.
Veamos en detalle estos 3 conceptos:
1) Las relaciones entre objetos son, precisamente, los enlaces que permiten a un
objeto relacionarse con aquellos que forman parte de la misma organización.
Las hay de dos tipos fundamentales:
_ Relaciones jerárquicas. Son esenciales para la existencia misma de la
aplicación porque la construyen. Son bidireccionales, es decir, un objeto es
padre de otro cuando el primer objeto se encuentra situado inmediatamente
encima del segundo en la organización en la que ambos forman parte;
asimismo, si un objeto es padre de otro, el segundo es hijo del primero. Una
organización jerárquica simple puede definirse como aquella en la que un
objeto puede tener un solo padre, mientras que en una organización
jerárquica compleja un hijo puede tener varios padres.
_ Relaciones semánticas. Se refieren a las relaciones que no tienen nada que
ver con la organización de la que forman parte los objetos que las
establecen. Sus propiedades y consecuencias solo dependen de losobjetos en sí mismos (de su
significado) y no de su posición en la
organización.
Veámoslo con un ejemplo: supongamos que vamos a construir un diccionario
informatizado que permita al usuario obtener la definición de una palabra cualquiera.
Supongamos que, en dicho diccionario, las palabras son objetos y que la organización
jerárquica es la que proviene de forma natural de la estructura de nuestros
conocimientos sobre el mundo.
La raíz del diccionario podría llamarse TEMAS. De éste término genérico,
descenderán 3 grandes ramas de objetos llamadas VIDA, MUNDO y HOMBRE. El
primero (VIDA) comprenderá las ciencias biológicas: Biología y Medicina. El segundo
(MUNDO), las ciencias de la naturaleza inerte: las Matemáticas, la Física, la Química y
la Geología. El tercero (HOMBRE) comprenderá las ciencias humanas: la Geografía,
la Historia, etc.
Aplicamos el ejemplo: establecemos la relación trabajo entre los objetos NEWTON
y ÓPTICA y la interpretaremos diciendo que significa que Newton trabajó en Óptica. La
relación es, evidentemente, semántica, pues no establece ninguna connotación
jerárquica entre NEWTON y ÓPTICA y su interpretación depende exclusivamente del
significado de ambos objetos.
La existencia de esta relación nos permitirá responder a preguntas como:
¿Quién trabajó en óptica?, ¿En qué trabajó Newton?, ¿Quién trabajó en Física?
Las dos primeras se deducen inmediatamente de la existencia de la relación
trabajo. Para la tercera observamos que si Newton trabajó en Óptica automáticamente
sabemos que trabajó en Física, por ser óptica una rama de la Física (en nuestro
diccionario, el objeto ÓPTICA es hijo del objeto FÍSICA). Entonces gracias a la POO
podemos responder a la tercera pregunta sin necesidad de establecer una relación
entre NEWTON y FÍSICA, apoyándonos sólo en la relación definida entre NEWTON y
ÓPTICA y en que ÓPTICA es hijo de FÍSICA. De este modo se elimina toda
redundancia innecesaria y la cantidad de información que tendremos que definir para
todo el diccionario será mínima.
2) Propiedades. Todo objeto puede tener cierto número de propiedades, cada una
de las cuales tendrá, a su vez, uno o varios valores.
En POO, las propiedades corresponden a las clásicas “variables” de la
programación estructurada. Son, por lo tanto, datos encapsulados dentro del objeto,
junto con los métodos (programas) y las relaciones (punteros a otros objetos).
Las propiedades de un objeto pueden tener un valor único o pueden contener un
conjunto de valores más o menos estructurados (matrices, vectores, listas, etc.).
Además, los valores pueden ser de cualquier tipo (numérico, alfabético, etc.) si el
sistema de programación lo permite.
Pero existe una diferencia con las "variables", y es que las propiedades se pueden
heredar de unos objetos a otros. En consecuencia, un objeto puede tener una
propiedad de maneras diferentes:
_ Propiedades propias. Están formadas dentro de la cápsula del objeto.
_ Propiedades heredadas. Están definidas en un objeto diferente, antepasado
de éste (padre, abuelo, etc.). A veces a estas propiedades se les llama
“propiedades miembro” porque el objeto las posee por el mero hecho de ser
miembro de una clase.
3) Métodos. Los métodos especifican la forma en que se controlan los datos de
un objeto. Los métodos, en un tipo de objeto sólo hacen referencia a la
estructura de datos de ese tipo de objeto. No deben tener acceso directo a las
estructuras de datos de otros objetos. Para utilizar la estructura de datos de
otro objeto, deben enviar un mensaje a éste.
Un objeto entonces es “una cosa” cuyas propiedades están representadas por
tipos de datos y su comportamiento por métodos.
El método es una operación que realiza el acceso a los datos. Podemos definir
método como un programa procedimental o procedural escrito en cualquier lenguaje,
que está asociado a un objeto determinado y cuya ejecución sólo puede
desencadenarse a través de un mensaje recibido por éste o por sus descendientes.
Sinónimos de “método”, son todos aquellos términos que se han aplicado
tradicionalmente a los programas, como procedimiento, función, rutina, etc. Sin
embargo, es conveniente utilizar el término “método” para que se distingan claramente
las propiedades especiales que adquiere un programa en el entorno POO, que afectan
fundamentalmente a la forma de invocarlo (únicamente a través de un mensaje) y a su
campo de acción, limitado a un objeto y a sus descendientes, aunque posiblemente no
a todos.
Los métodos son programas, por lo tanto, tienen argumentos o parámetros.
Como los métodos pueden heredarse de unos objetos a otros, un objeto puede
disponer de un método de dos maneras diferentes:
_ Métodos propios. Están incluidos dentro de la cápsula del objeto.
_ Métodos heredados. Están definidos en un objeto diferente, antepasado de
éste (padre, abuelo, etc.). A veces estos métodos se llaman métodos
miembro porque el objeto los posee por el mero hecho de ser miembro de
una clase.

Demonios.
Es un tipo especial de métodos, relativamente poco frecuente en los sistemas de
POO, que se activa automáticamente cuando sucede algo especial. Es decir, es un
programa, como los métodos ordinarios, pero se diferencia de estos porque su
ejecución no se activa con un mensaje, sino que se desencadena automáticamente
cuando ocurre un suceso determinado: la asignación de un valor a una propiedad de
un objeto, la lectura de un valor determinado, etc.
Los demonios, cuando existen, se diferencian de otros métodos por que no son
heredables y porque a veces están ligados a una de las propiedades de un objeto,
más que al objeto entero.

Abstracción.
La Abstracción es una descripción especial simplificada de un sistema que hace
énfasis en ciertos rasgos y suprime otros.
La buena abstracción es aquella que logra hacer énfasis en los detalles
significativos o relevantes de la solución y discrimina cualquier otra característica. Con
esto se consigue un mapeo de los objetos del mundo real a los objetos del sistema.
Por ejemplo, la perspectiva de ver a un gato es muy distinta entre una abuela
amorosa y un médico veterinario. La abuela hará una abstracción fijándose en rasgos
afectivos y de cuidado mientras que el veterinario lo verá como un objeto anatómicofisiológico
de estudio.

Modularidad.
La Modularidad es una partición funcional de todo el sistema. Cada módulo o parte
del sistema debe contar tanto con una funcionalidad clara y relativamente sencilla
como con una facilidad de combinarse con otros módulos.
Condicionantes tales como limitación de memoria, o las características de los
compiladores o lenguajes particulares, inducen a la modularidad, pero el principio en la
Tecnología Orientada a Objetos, es dividir funcionalmente buscando la interrelación
más rica entre módulos.
Una división temática de los módulos es una manera muy conveniente de
equilibrar la modularización. De esta manera, un módulo puede ser el de los cálculos
numéricos mientras que otro, el de las operaciones con cadenas, o bien, un módulo
puede ser el de las variables generales y de arranque y configuración de un sistema,
Un segundo módulo alojará las clases primitivas, de las cuales se derivarán siempre
para su uso otras clases y un tercer módulo tendrá las clases que usan a las
primitivas.
En el caso de nuestro gato, lo veríamos descompuesto en unidades funcionales.
Su corazón funciona muy bien, al igual su cuello y cola, cada parte debe ensamblar
perfectamente con las de su entorno para formar un gato completo.

Encapsulación de objetos.
En forma sucinta y concreta, podemos decir que es el “principio por el cual se
deben modelar al mismo tiempo y de forma inseparable Métodos y Datos”.
La Interfaz representa la frontera y el lugar de paso en la comunicación del objeto
con el mundo exterior.
El empaque conjunto de datos y métodos se llama encapsulado. El objeto esconde
sus datos de los demás objetos y permite el acceso a los datos mediante sus propios
métodos. Esto recibe el nombre de “ocultamiento de información”.
El encapsulamiento evita la corrupción de los datos de un objeto. Si todos los
programas pudieran tener acceso a los datos de cualquier forma que quisieran los
usuarios, los datos se podrían corromper o utilizar de mala manera. El encapsulado
protege los datos del uso arbitrario o accidental.
El encapsulado oculta los detalles de su implantación interna a los usuarios de un
objeto. Los usuarios se dan cuenta de las operaciones que puede solicitar del objeto,
pero desconocen los detalles de cómo se lleva a cabo la operación. Todos los detalles
específicos de los datos del objeto y la codificación de sus operaciones están fuera del
alcance del usuario.
Así, encapsulado es el resultado (o acto) de ocultar los detalles de implantación de
un objeto respecto de su usuario.
El encapsulado, al separar el comportamiento del objeto de su implantación,
permite la modificación de ésta sin que se tengan que modificar las aplicaciones que lo
utilizan.
Esto no quiere decir, sin embargo, que sea imposible conocer lo necesario
respecto a un objeto y a lo que él contiene. Si así fuera, no se podría hacer gran cosa
con él. Lo que sucede es que las peticiones de información a un objeto deben
realizarse a través de mensajes dirigidos a él, con la orden de realizar la operación
pertinente. La respuesta a estas órdenes será la información requerida, siempre que el
objeto considere que quien envía el mensaje está autorizado para obtenerla.

Reutilización.
El hecho de que cada objeto sea una cápsula, facilita enormemente que un objeto
determinado pueda ser transportado a otro punto de la organización, o incluso a otra
organización totalmente diferente que precise de él. Si el objeto ha sido bien
construido, sus métodos seguirán funcionando en el nuevo entorno sin problemas.
Esta cualidad hace que la POO sea muy apta para la reutilización de programas.

Clase.
La clase es una colección de objetos con características comunes. Las clases son
entidades conceptuales que sirven para abstraer y modelizar un sistema.
Toda clase posee 2 tipos o clases de componentes:
_ Una estática: los datos. Caracterizan los posibles estados que pueden
adoptar los Objetos de la Clase en un instante determinado.
_ Otra dinámica: los métodos. Caracterizan los posibles comportamientos de
los Objetos de la Clase a lo largo de su existencia.
Los criterios de clasificación de una Clase son:
_ Atributos: Variables que tomarán ciertos valores (Datos) en un estado del
Objeto. Definen la estructura o componente estática de los Objetos.
_ Eventos: Estímulos ante los que reaccionan los Objetos cambiando de
Estado.
_ Funciones: Ante un evento, actúan sobre los datos haciendo que el Objeto
cambie de estado. Determinan la componente dinámica de los Objetos.
Así, una clase es una creación de un tipo de objeto. Especifica una estructura de
datos y los métodos operativos permisibles que se aplican a cada uno de sus objetos.

Herencia.
Es una relación transitiva entre clases, que permite a un objeto de una clase utilizar
como propios los datos y métodos definidos en otra clase. Por ejemplo: La clase
cliente hereda de la clase persona sus métodos y datos.
La clase padre se suele denominar superclase.
La clase hija se suele denominar subclase, descendiente, derivada,
especialización o heredada.
La Subclase consta de dos tipos de características:
_ Estructura o comportamiento heredado de la superclase.
_ Estructura o comportamiento propios.
La subclase se especializa de las siguientes formas:
_ Enriquecimiento: Estructura o comportamiento añadidos como propios.
_ Ocultación: Estructura o comportamiento heredado y anulado
_ Sustitución: Estructura o comportamiento heredado y redefinido
Ejemplos de herencia.
Dijimos que un tipo de objeto de alto nivel puede especializarse en tipos de objetos
de bajo nivel.
Por ejemplo, el tipo de objeto persona, puede tener subtipos estudiante y
empleado. A su vez, el tipo de objeto estudiante puede tener como subtipo estudiante
de pre-grado y estudiante de post-grado, mientras que empleado puede tener como
subtipo a académico y administrativo. Existe de este modo una jerarquía de tipos,
subtipos, sub-subtipos, etc.
Entonces, una clase implanta el tipo de objeto. Una subclase hereda propiedades
de su clase padre; una sub-subclase hereda propiedades de las subclases; etc.
Una subclase puede heredar la estructura de datos y los métodos de su
superclase. También tiene sus propios métodos e incluso sus propios tipos de datos.
Tipos de herencia.
_ Simple: Se hereda de una sola clase.
_ Múltiple: Se hereda de dos o más clases.

Polimorfismo.
Una de las características fundamentales de la POO es el polimorfismo, que no es
otra cosa que la posibilidad de construir varios métodos con el mismo nombre, pero
con relación a la clase a la que pertenece cada uno, con comportamientos diferentes.
Esto conlleva la habilidad de enviar un mismo mensaje a objetos de clases
diferentes. Estos objetos recibirían el mismo mensaje global pero responderían a él de
formas diferentes; por ejemplo, un mensaje "+" a un objeto ENTERO significaría suma,
mientras que para un objeto STRING significaría concatenación.

Jerarquías.
Organización de los objetos
En principio, los objetos forman siempre una organización jerárquica, en el sentido
de que ciertos objetos son superiores a otros de cierto modo. Existen varios tipos de
jerarquías: serán simples cuando su estructura pueda ser representada por medio de
un "árbol". En otros casos puede ser más compleja.
En cualquier caso, sea la estructura simple o compleja, podrán distinguirse en ella
3 niveles de objetos:
_ La raíz de la jerarquía: Se trata de un objeto único y especial. Este se
caracteriza por estar en el nivel más alto de la estructura y suele recibir un
nombre muy genérico, que indica su categoría especial, como por ejemplo
objeto madre, raíz o entidad.
_ Los objetos intermedios: Son aquellos que descienden directamente de la
raíz y que a su vez tienen descendientes. Representan conjuntos o clases
de objetos, que pueden ser muy generales o muy especializados, según la
aplicación. Normalmente reciben nombres genéricos que denotan al
conjunto de objetos que representan, por ejemplo, VENTANA, CUENTA,
FICHERO.
_ Los objetos terminales: Son todos aquellos que descienden de una clase o
subclase y no tienen descendientes. Suelen llamarse casos particulares,
instancias o ítem, porque simbolizan los elementos del conjunto
representado, por la clase o subclase a la que pertenecen.
La jerarquía es el orden por niveles de todas las abstracciones. Las dos partes más importantes de la
jerarquía son la estructura de clases y la
estructura de objetos. La primera establece todas las relaciones de herencia en sus
modalidades permitidas, la segunda, la dinámica de mensajes.
La organización de todas las abstracciones logradas en un sistema está en un
orden riguroso que categoriza objetos de un mismo o semejante tipo dentro de una
misma categoría.
Así al hablar de manzanas, podremos hablar de las amarillas, las rojas, o las
verdes, pero en otra categoría hablaremos de sus componentes, pulpa, piel, semilla, y
posiblemente en una categoría más inferior de sus tejidos. Ni los tejidos sabrán a qué
componente pertenecen ni los componentes a qué manzana pertenecen.
La abstracción superior no sabe de sus constituyentes ínfimos y viceversa. Cada
abstracción debe connotar un nivel.

Mensajes
Para que un objeto haga algo, le enviamos una solicitud. Esta hace que se
produzca una operación. La operación ejecuta el método apropiado y, de manera
opcional, produce una respuesta. El mensaje que constituye la solicitud contiene el
nombre del objeto, el nombre de una operación y, a veces, un grupo de parámetros.
La programación orientada a objetos es una forma de diseño modular en la que
con frecuencia el mundo se piensa en términos de objetos, operaciones, métodos y
mensajes que se transfieren entre tales objetos. Un mensaje es una solicitud para que
se lleve a cabo la operación indicada y se produzca el resultado.
Los objetos pueden ser muy complejos, puesto que pueden contener muchos subobjetos,
éstos a su vez pueden contener otros, etc. La persona que utilice el objeto no
tiene que conocer su complejidad interna, sino la forma de comunicarse con él y la
forma en que le responde.

LENGUAJE DE PROGRAMACIÓN C++

INTRODUCCIÓN
Para enfrentarse a las complejidades del mundo real, el ser humano ha desarrollado la capacidad
de generalizar, clasificar y generar abstracciones. Así, tenemos un vocabulario donde muchos de
los sustantivos representan clases de objetos.
Los objetos de cada clase comparten ciertos atributos o rasgos de comportamiento.

Por ejemplo, al tratarse algún tema relacionado con las aves no es necesario referirnos a alguna
característica de una ave en particular sino a los atributos que comparten todas ellas.
Los lenguajes de Programación Orientada a Objetos pueden dividirse en dos grupos:
El primero formado por los llamados lenguajes "puros" (llamados así debido a que sólo permiten
programar con el paradigma de la Programación Orientada a Objetos) dentro del cual se
encuentran SmallTalk y Actor, entre otros.
El segundo grupo lo forman los lenguajes que permiten, además de la Programación Orientada a
Objetos, la programación procedimental, razón por la que se les llama "híbridos". A este grupo
pertenece el lenguaje C++.
Para el manejo de los lenguajes de Programación Orientada a Objetos es necesario estudiar los
conceptos de :

ENCAPSULAMIENTO
HERENCIA
POLIMORFISMO

En las siguientes secciones se estudia detalladamente cada una de ellas.


La Programación Orientada a Objetos es un método de programación que intenta imitar la forma
en que modelamos el mundo real y tiene como meta principal mejorar la eficiencia y productividad
de los grupos de programadores. Muchos expertos afirman que la Programación Orientada a
Objetos ayuda a reducir la complejidad, especialmente en programas grandes ( de 10,000 líneas o
más ), además de que permite a los programadores reutilizar el código existente en lugar de
reescribir módulos que ya trabajan correctamente.

ENCAPSULAMIENTO

El encapsulamiento consiste en poner juntos los datos y las funciones dentro de un objeto de tipo
clase.
El modelo procedimental puede representarse como en la figura.

- Modelo procedimental.

donde se observa que los datos y el código se manejan como partes separadas. El programa (
código ) es alimentado con los datos para que produzca resultados.
Por otra parte, el modelo orientado a objetos puede representarse como en la figura 8.2.

- Modelo de la Programación Orientada a Objetos.

donde DATOS y CODIGO se han unido para formar un OBJETO, el cual va a producir ciertos
RESULTADOS, de acuerdo al MENSAJE que se le envíe.
Esto es, un objeto de ciertas características va a comportarse de acuerdo a su clase al recibir un
mensaje específico.
Como se logra el ENCAPSULAMIENTO en el lenguaje C++ ?
Podemos pensar que, así como se requieren tipos para definir las variables en un enfoque
procedimental, se requieren "tipos" o "moldes" para crear los objetos.

En el lenguaje C++ esos moldes se crean por medio de las palabras reservadas : class, struct y
union .
La Geometría representa un campo idóneo para ejemplificar el manejo de objetos. Las definiciones
para cada una de las figuras se hacen a través de enunciados que incluyen la expresión: " .. es el
lugar geométrico de todos los puntos que ...". Como puede verse, el concepto de punto es la base
para la definición de cualquier figura geométrica.
La representación de las figuras geométricas, en la pantalla de un monitor, se hace por medio de
los pixeles, que son los elementos básicos ( puntos iluminados ) utilizados para representar los
gráficos en la pantalla.
Con base a estos conceptos, en la siguiente sección vamos a crear una clase base llamada punto.
Los objetos de tipo punto podrán situarse en cualquier lugar de la pantalla utilizando un par de
coordenadas ( x,y ).

DEFINICIÓN DE CLASES

La definición de la clase punto en C++ puede tomar la siguiente forma :

class punto
{
int x,y ; // MIEMBROS DATO
};

Aquí no se está haciendo uso del encapsulamiento, puesto que no se ha declarado ninguna
función miembro dentro de la clase
FUNCIONES MIEMBRO

En otros lenguajes orientados a objetos, a las funciones miembro se les conoce como métodos .
Para incluir una función miembro en la definición de una clase, existen dos formas :

1.- Definir la función dentro de la definición de la clase.


Por ejemplo :

class punto
{
int x,y ;
int dax() { return x ; } // FUNCION EN LINEA
};

2.- Declarar la función dentro de la definición de la clase y escribir la definición de la función fuera
de la definición de la clase.
Por ejemplo :

class punto
{
int x,y ;
int dax() ; // DECLARACION DE LA FUNCION MIEMBRO
};

int punto::dax() // DEFINICION DE LA FUNCION MIEMBRO


{
return x ;
}

En la línea de cabecera de la definición de la función miembro dax() se utiliza el operador de


resolución de ambito :: . Este operador indica que la función dax() es una función miembro de la
clase punto.

CONSTRUCTORES

Los constructores y los destructores son dos tipos especiales de funciones miembro.

Un constructor especifica la manera en que será creado e inicializado un nuevo objeto de cierta
clase.
Los constructores en C++ pueden ser definidos por el usuario ó generados por el lenguaje.
El compilador de C++ invoca automáticamente al constructor apropiado cada vez que se defina un
nuevo objeto.
Esto puede ocurrir en una declaración de datos, cuando se copia un objeto o a través de la
asignación dinámica de memoria a un nuevo objeto por medio del operador new.
Para añadir un constructor a la clase punto escribimos :

class punto
{
int x,y ;
int dax() { return x ; }
int day() { return y ; }
punto(int nx, int ny) ; // DECLARACION DEL CONSTRUCTOR
};

punto::punto(int nx, int ny) // DEFINICION DEL CONSTRUCTOR


{
x = nx ;
y = ny ;
}

DESTRUCTORES

Los destructores destruyen a los objetos creados, liberando la memoria asignada. Pueden ser
invocados explícitamente por medio del operador delete.

Siguiendo con el ejemplo de la clase punto :

class punto
{
int x,y ;
int dax() { return x ; }
int day() { return y ; }
punto(int nx, int nx) ;
~punto() ; // DECLARACION DEL DESTRUCTOR
};

punto::punto(int nx, int nx)


{
x = nx ;
y = ny ;
}

punto::~punto() // DEFINICION DEL DESTRUCTOR


{
delete x ;
delete y ;
}

AMBITO DE CLASES

El término ámbito se refiere al área dentro de un programa donde un identificador dado es


accesible.
Para controlar la manera en que son accesados los miembros de las clases, C++ ha introducido el
concepto de ámbito de clase . Todos los miembros de una clase están en el ámbito de esa clase,
de manera que cualquier miembro de una clase puede referenciar a cualquier otro miembro de la
misma clase.
Las funciones miembro de una clase tienen acceso irrestricto a los miembros dato de esa misma
clase. El acceso a los datos y funciones miembro de una clase exterior al ámbito de la clase es
controlado por el programador a través de los especificadores de acceso.

ESPECIFICADORES DE ACCESO

Para el control de acceso a los miembros de una clase, se cuenta con tres palabras clave : private:
, protected: y public: .
La palabra clave apropiada ( incluyendo los dos puntos ) debe colocarse antes de las
declaraciones que van a afectarse.

private: Los miembros que siguen pueden ser accesados solamente


por las funciones miembro declaradas dentro de la
misma clase.
protected: Los miembros que siguen pueden ser accesados por
funciones miembro declaradas dentro de la misma clase,
y por funciones miembro de clases derivadas de esta
clase.

public: Los miembros que siguen pueden ser accesados desde


cualquier lugar dentro del mismo ámbito de la
definición de la clase.

Por predeterminación, los miembros de las clases de tipo class son private , los de las clases de
tipo struct son public.
Por ejemplo, para hacer que las funciones miembro de la clase punto puedan ser accesadas desde
cualquier lugar, escribimos :

class punto
{
int x, y ; // privadas POR PREDETERMINACION

public: // LO QUE SIGUE ES público


int dax() { return x; }
int day() { return y; }
punto ( int nx, int ny ) ;
~punto();
}
punto::punto(int nx, int ny)
{
x = nx ;
y = ny ;
}

punto::~punto()
{
delete x ;
delete y ;
}

ACCESO PRIVILEGIADO

Una clase puede darle a otra clase o función acceso privilegiado a sus áreas privadas. Tal acceso
debe darse explícitamente, declarando la otra clase o función por medio del modificador friend
(amigo). Los amigos son tratados como si fueran miembros de la clase y tienen acceso irrestricto a
las áreas privadas de los objetos.
Por ejemplo :

class cosa
{
private:
int dato ;
public:
friend void carga( cosa t , int x ) ;
};

void carga ( cosa t , int x )


{
t.dato = x ;
}
La definición de la clase cosa contiene el prototipo para la función carga y la diseña como un
amigo. La función carga no es un miembro de la clase cosa, sino que es una función independiente
a la que se le ha dado acceso especial a los miembros privados de los objetos de tipo cosa.
Debido a que es un amigo de la clase cosa, carga puede accesar directamente los datos miembro
privados de su parámetro t.
Una clase completa puede hacerse un amigo de otra, tal como en:

class perico ;

class paloma
{
public:
friend class perico ;
};

class perico
{
//CUALQUIER COSA
};

#include <iostream.h>

class punto
{
int x,y ;

public:
punto(int nx, int ny) { x = nx ; y = ny ; }
int dax() { return x ; }
int day() { return y ; }
};

void main()
{
int datox, datoy ;

cout << "COORDENADA EN X : " ; cin>> datox ;


cout << "COORDENADA EN y : " ; cin>> datoy ;

punto objeto(datox , datoy) ;//INVOCA AL CONSTRUCTOR punto

cout << "X=" << objeto.dax() ;


cout << '\n' ;
cout << " Y=" << objeto.day() ;
cout << '\n' ;
}
</pre>
</B></I><h4>Listado 8.1.- Utilización de la clase punto.</h4>
<TABLE BORDER=0 WIDTH=" 99%">

HERENCIA

En la Programación Orientada a Objetos, la herencia es un concepto semejante al de la herencia


genética que se dá en la naturaleza.
En el reino animal, por ejemplo, podemos hablar de una clase base llamada aves, cuyos miembros
poseen:

- plumas
- alas
- pico

Sin embargo, no todas las aves son iguales, por lo que se tiene que recurrir a las clases derivadas
para distinguir unas aves de otras, como se muestra en la figura 8.3.

Jerarquía de clases a partir de la clase aves.

Cada una de las clases derivadas conserva las características de la clase base aves, pero posee
algunas otras que la distingue de las demás clases.
Hasta aquí, hemos hablado solamente de clases de aves no de las aves en sí. No nos hemos
referido a ninguna ave en particular, por lo que pudieramos decir que hemos estado hablando de
los "tipos de aves".
Una vez que se tienen los tipos, podemos llevar a cabo la construcción de aves. Cuando tales aves
hayan cumplido su misión, podremos destruirlas.
Esto último puede parecer demasiado cruel en el mundo animal, pero en el caso de la
Programación Orientada a Objetos es lo más práctico, pues resultaría un gasto innecesario de
memoria el conservar los objetos que ya no van a utilizarse .
Las clases, al ser consideradas como tipos, pueden utilizarse para construir bloques de programa.
Si un tipo no cumple con las especificaciones de un nuevo programa, se puede utilizar para crear
otro nuevo. Este nuevo tipo se modifica para usarse en la elaboración de nuevos objetos, y a su
vez queda disponible para elaborar otros tipos, según se necesite.
Es en esta creación de nuevos tipos donde se llega a la utilización de los modelos jerárquicos de
clasificación.
En C++, la herencia se maneja por medio de la derivación de clases.

DERIVACIÓN DE CLASES

La derivación de clases en C++ se rige por la siguiente sintaxis :

class derivada : modificador_de_acceso base


{
.....
.....
}
modificador_de_acceso es opcional y puede ser public o private.

MODIFICADORES DE ACCESO

Los modificadores de acceso se utilizan para cambiar la accesibilidad de los miembros heredados
de acuerdo a lo que se observa en la tabla 8.1 :
EN CLASE BASE MODIF. DE ACCESO EN CLASE DERIVADA
private private inaccesible
protected private private
public private private
private public inaccesible
protected public protected
public public public

Tabla 8.1.- Modificadores de acceso para las clases derivadas.

Cuando se utiliza la palabra class, el modificador de acceso está predefinido como private ; cuando
se utiliza la palabra struct, está predefinido como public.
Al crear nuevas clases que dependen de clases existentes, debe asegurarse de que entiende la
relación existente entre las clases base y derivada. Para esto es vital el entendimiento de los
niveles de acceso conferidos por los especificadores private , protected y public .
El C++ permite pasar los derechos de acceso de padres a hijos sin exponer los datos a quienes no
son miembros ni amigos de la familia.
El nivel de acceso de un miembro de una clase base, no es necesariamente el mismo cuando se
maneja en el ámbito de la clase base que cuando se maneja en el ámbito de la clase derivada. El
programador tiene el control sobre los niveles de acceso en todos los ámbitos.
Como puede observarse en la tabla 8.1, el especificador private convierte los miembros public y
protected de la clase base a miembros private en la clase derivada. Los miembros con
especificador private en la clase base, mantienen ese especificador en la clase derivada.
Aunque en las clases definidas por medio de la palabra class el nivel de acceso esta
predeterminado como private, conviene explicitar los niveles de cada uno de los miembros de cada
clase, para evitar confusiones.
Por lo tanto, puede escribirse :

class ejemplo
{
private:
int num ;
float costo ;

public:
void valida( char * ) ;
int precio( float ) ;
};

Una clase derivada hereda todos los miembros de su clase base, pero puede utilizar solamente los
miembros protegidos y públicos de su clase base.
Los miembros privados de la clase base no están disponibles directamente a través de los
miembros de la clase derivada.
Al usar la derivación public, los miembros protegidos de la clase base permanecen protegidos en la
clase derivada; así que no estarán disponibles desde el exterior, excepto para otras clases
derivadas públicamente y para los miembros y clases amigos.

MANEJO DE CLASES EN MODULOS


Las clases pueden empacarse en módulos para su utilización posterior en el desarrollo de
programas.
Una clase es inherentemente modular con sus miembros dato, funciones miembro y control de
acceso. En el desarrollo de un programa, a veces tiene sentido poner las declaraciones, para cada
clase o grupo relacionado de clases, por separado en un archivo de cabecera; y en otro las
definiciones para las funciones miembro que no estén en línea.
En el listado 8.2 se presenta el archivo de cabecera PIXEL.H , donde, a partir de la clase base
punto, se deriva otra clase llamada pixel que nos servirá para visualizar u ocultar puntos en
cualquier lugar de la pantalla.

///////////////////////////////////////////////////////////
// PIXEL.H : CONTIENE LAS CLASES : //
// //
// punto QUE MANEJA LA LECTURA Y COLOCACION //
// DE UN PUNTO. //
// //
// pixel MANEJA LA VISIBILIDAD DE UN PUNTO EN //
// LA PANTALLA. //
///////////////////////////////////////////////////////////

enum booleano { falso, verdadero } ;

class punto
{
protected: //PERMITE QUE LAS CLASES DERIVADAS HEREDEN
//DATOS privados.
int x , y ;

public: //LAS SIGS.FUNCIONES PUEDEN ACCESARSE DESDE


//EL EXTERIOR.

punto(int nx, int ny) ;


int dax();
int day();

};

// CLASE DERIVADA DE punto. LA DERIVACION public SIGNIFICA


// QUE x , y ESTARAN PROTEGIDAS DENTRO DE pixel.

class pixel : public punto


{
protected: //LAS CLASES DERIVADAS DE pixel NECESITAN
//ACCESO A ESTOS MIEMBROS DATO.
booleano visible ;

public:
pixel(int nx, int ny); // CONSTRUCTOR
void mostrar();
void ocultar();
boleano es_visible();
void mover_hacia(int nuevax, int nuevay);
};
Listado 8.2.- Archivo PIXEL.H

Nótese la utilización de protected en lugar de private para ciertos elementos, con lo que se hace
posible la utilización del archivo PIXEL.H en aplicaciones que manejen clases derivadas de punto y
pixel.
Las definiciones para las funciones miembro de las clases punto y pixel se almacenan en el archivo
PUNTO2.CPP del listado 8.3.

/////////////////////////////////////////////////////////////
// PUNTO2.CPP : CONTIENE LA DEFINICION PARA LAS FUNCIONES //
// MIEMBRO DE LAS CLASES punto y pixel //
/////////////////////////////////////////////////////////////

#include "pixel.h"
#include <graphics.h>

// DEFINICION DE LAS FUNCIONES MIEMBRO PARA LA CLASE punto

punto::punto(int nx , int ny)


{
x = nx ;
y = ny ;
};

int punto::dax(void)
{
return x ;
};

int punto::day(void)
{
return y ;
};

// DEFINICION DE LAS FUNCIONES MIEMBRO PARA LA CLASE pixel

pixel::pixel(int nx, int ny) : punto(nx,ny)


{
visible = falso ;
};

void pixel::mostrar(void)
{
visible = verdadero ;
putpixel(x,y, getcolor()); // USA EL COLOR PREDEFINIDO.
// SUPONE QUE EN main() SE
// INICIALIZAN LOS GRAFICOS.
};

void pixel::ocultar(void)
{
visible = falso ;
putpixel(x,y,getbkcolor()); // USA EL COLOR DEL FONDO.
};

booleano pixel::es_visible(void)
{
return visible ;
};
void pixel::mover_hacia(int nuevax, int nuevay)
{
ocultar(); // HACE INVISIBLE AL PUNTO ACTUAL
x = nuevax ; // CAMBIA LAS COORDENADAS x,y .
y = nuevay ;
mostrar(); // MUESTRA EL PUNTO EN LA NUEVA LOCALIDAD.
};
Listado 8.3.- Definición de las clases punto y pixel.

Observe que, para definir el constructor de la clase derivada, se hace referencia al constructor de
la clase base como en :
pixel::pixel(int nx,intny) : punto(nx,ny)
Esto significa que, para construir un objeto de la clase pixel, primero se invoca al constructor de la
clase base punto con los argumentos nx y ny creando e inicializando los miembros dato x,y.
Posteriormente se invoca al constructor pixel, creando e inicializando el miembro dato visible.

Note que la referencia al constructor de la clase base aparece en la definición, no en la


declaración, del constructor de la clase derivada.
Si no se ha definido un constructor para una clase x, el C++ generará un constructor predefinido de
la forma :
x::x();
esto es, un constructor sin argumentos.
El listado 8.4 contiene un programa que muestra la utilización de la clase pixel .

////////////////////////////////////////////////////////////
// PIXEL.CPP : APLICACION ELEMENTAL DE LA CLASE pixel //
// //
////////////////////////////////////////////////////////////
#include <graphics.h>
#include <conio.h>
#include "pixel.h"

int main()
{
// INICIALIZACION DEL SISTEMA DE GRAFICOS

int manej = DETECT, modo ;

initgraph ( &manej, &modo, "c:\\borlandc\\bgi");

// ENCIENDE, CAMBIA Y APAGA PIXELES

pixel unpixel(100,50) ; // DEFINE unpixel CON VALORES


// INICIALES x=100, y=50
unpixel.mostrar(); // unpixel SE ENCIENDE
getch() ; // ESPERA UNA PULSACION DE TECLA
unpixel.mover_hacia(300,150); // MUEVE unpixel A 300,150
getch();
unpixel.ocultar(); // OCULTA A unpixel
getch();
closegraph();
return 0 ;
}
Listado 8.4.- Utilización de la clase pixel.

Como podrá observarse, no se ha incluido el archivo PUNTO2.CPP, por lo que este programa
deberá compilarse utilizando la opción PROJECT del Ambiente Integrado de Desarrollo ( IDE ) del
C++ de Borland, creando un archivo de proyecto PIXEL.PRJ que incluya los archivos
GRAPHICS.H, PUNTO2.CPP y PIXEL.CPP .
Uno de los atractivos de las clases es que pueden utilizarse para construir otras clases que
respondan a las necesidades de una nueva aplicación.
En el ejemplo que se muestra en el listado 8.5 se hace uso de la clase pixel para derivar una nueva
clase llamada circulo, la cual contendrá funciones miembro para mostrar, ocultar, mover, expander
y contraer circulos.

/////////////////////////////////////////////////////////////
// CIRCULO.CPP : MANEJO DE LA CLASE circulo DERIVADA DE LA //
// CLASE pixel //
/////////////////////////////////////////////////////////////

#include <graphics.h>
#include <conio.h>
#include "pixel.h"

class circulo : pixel // DERIVADA PRIVADAMENTE DE pixel


{
int radio ;

public:
circulo(int nx, int ny, int nradio);
void mostrar(void);
void ocultar(void);
void expander(int cantexp);
void contraer(int cantcont);
void mover_hacia(int nvax, int nvay);
};

circulo::circulo(int nx,int ny,int nradio) : pixel(nx,ny)


{
radio = nradio;
};

void circulo::mostrar(void)
{
visible = verdadero;
circle(x,y,radio); // DIBUJA EL CIRCULO
};

void circulo::ocultar(void)
{
unsigned int colortemp ; // PARA GUARDA EL COLOR ACTUAL
colortemp = getcolor(); // GUARDA EL COLORR ACTUAL
setcolor(getbkcolor()); // CAMBIA AL COLOR DEL FONDO
visible = falso ;
circle(x,y,radio); // DIBUJA EL CIRCULO CON EL COLOR
// DEL FONDO PARA BORRARLO
setcolor(colortemp); // RESTABLECE EL COLOR ORIGINAL
};
void circulo::expander(int cantexp)
{
ocultar(); // BORRA EL CIRCULO ACTUAL
radio += cantexp; // EXPANDE EL RADIO
if( radio <0 ) // EVITA RADIOS NEGATIVOS radio="0" ; mostrar(); // DIBUJA EL NUEVO
CIRCULO }; void circulo::contraer(int cantcontr) { expander(-cantcontr); }; void
circulo::mover_hacia(int nvax, int nvay) { ocultar(); // BORRA EL CIRCULO ACTUAL x="nvax" ; //
ASIGNA NUEVAS COORDENADAS y="nvay" ; mostrar(); // DIBUJA EL CIRCULO EN LA NUEVA
POSICION }; void main() { int manej="DETECT" , modo ; initgraph(&manej, &modo,
"c:\\borlandc\\bgi"); circulo uncirculo(100,200,50); uncirculo.mostrar(); getch();
uncirculo.mover_hacia(200,250); getch(); uncirculo.expander(50); getch(); uncirculo.contraer(75);
getch(); closegraph(); }

HERENCIA MULTIPLE

Todos los lenguajes de Programación Orientada a Objetos manejan la herencia en la forma que
hemos visto hasta aquí, donde cada clase derivada tiene sólo una clase base. Sin embargo,
algunos de ellos permiten manejar lo que se llama herencia múltiple, en la cual se permite que una
clase derivada pueda tener varias clases base.
El mecanismo de herencia múltiple fué una de las principales características añadidas a la versión
2.0 de C++.

Como un ejemplo del manejo de la herencia múltiple, consideremos el problema de tener que
desplegar texto dentro de un círculo.
Al principio podemos estar tentados a añadir una cadena de caracteres como miembro dato a la
clase circulo y después agregarle código a circulo::mostrar para que despliegue el circulo con texto
adentro.
Pero el texto y el círculo son dos cosas diferentes. Cuando se piensa en texto, se piensa en
tamaños y tipos de caracteres y en otros atributos que nada tienen que ver con los círculos.
Sin embargo, podemos derivar una nueva clase directamente de la clase circulo y de otra clase
que permita darle capacidad de manejar texto.
En la figura 8.4 se ilustra este enfoque, el cual se detalla en el programa MULTI.CPP del listado
8.6.

////////////////////////////////////////////////
// MULTI.CPP : ILUSTRA LA HERENCIA MULTIPLE //
////////////////////////////////////////////////

#include <graphics.h>
#include <conio.h>
#include <string.h>
#include "pixel.h"

class circulo : public pixel


{

protected:
int radio;

public:
circulo(int nx, int ny, int nradio) ;
void mostrar(void);
};

class mensaje : public punto // DESPLIEGA UN MENSAJE


{

char *mens ; // MENSAJE A DESPLEGAR


int fuente ; // FUENTE A UTILIZAR
int campo ; // TAMAÑO DEL CAMPO PARA EL TEXTO

public:
mensaje( int mensx, int mensy, int mensf, int mensc,
char *texto );
void mostrar(void); // MUESTRA EL MENSAJE
};

class multi : circulo, mensaje // HEREDA DE AMBAS


{ // CLASES.
public:
multi(int mcircx, int mcircy, int mcircr, int fuente,
char *mens);
void mostrar(void); // MUESTRA CIRCULO CON MENSAJE
};

// DEFINICION DE LAS FUNCIONES MIEMBRO PARA LA CLASE circulo


circulo::circulo(int nx,int ny, int nradio) : pixel(nx,ny)
{
radio = nradio ;
};

void circulo::mostrar(void)
{
visible = verdadero ;
circle(x,y,radio) ; // DIBUJA EL CIRCULO
};

// DEFINICION DE LAS FUNCIONES MIEMBRO PARA LA CLASE mensaje


mensaje::mensaje(int mensx, int mensy, int mensf,
int mensc, char *texto )
: punto(mensx,mensy)
{
fuente = mensf ;
campo = mensc ;
mens = texto ; // APUNTA A MENSAJE
};
void mensaje::mostrar(void)
{
int tam = campo / ( 8 * strlen(mens)) ; // 8 PIXELES POR
// CARACTER
settextjustify(CENTER_TEXT, CENTER_TEXT);// CENTRA TEXTO
settextstyle(fuente, HORIZ_DIR, tam) ; //AGRANDA SI tam >1
outtextxy(x,y, mens) ; // DESPLIEGA MENSAJE
};

// DEFINICION DE LAS FUNCIONES MIEMBRO PARA LA CLASE multi

multi::multi(int mcircx, int mcircy, int mcircr,


int fuente, char *mens)
: circulo(mcircx, mcircy, mcircr) ,
mensaje(mcircx, mcircy, fuente,
2*mcircr, mens)
{
};

void multi::mostrar(void)
{
circulo::mostrar();
mensaje::mostrar();
};

// DEFINICION DE LA FUNCION PRINCIPAL

void main()
{
int manej = DETECT, modo ;
initgraph(&manej, &modo, "c:\\borlandc\\bgi");
multi uno(250,100,25,SANS_SERIF_FONT,"Tu");
uno.mostrar();
multi dos(250,150,100,TRIPLEX_FONT,"Tierra");
dos.mostrar();
multi tres(250,250,225,GOTHIC_FONT,"Universo");
tres.mostrar();
getch();
closegraph();
}

POLIMORFISMO

Las funciones miembro son las que determinan el comportamiento de un objeto. Esto conlleva la
idea de que, si queremos que un objeto se comporte de manera diferente, primero habrá que
modificar la clase o "molde", agregándole la función miembro que determina el comportamiento
deseado, para, posteriormente, crear un objeto de la clase modificada.
En los ejemplos mostrados hasta aquí, todos los objetos tienen la habilidad necesaria para
mostrarse en la pantalla. Sin embargo, cada tipo de objeto difiere de los demás en la forma de
mostrarse.
Por ejemplo, un pixel puede mostrarse con sólo indicarle las coordenadas x,y correspondientes a
su posición de la pantalla. En cambio, un círculo requiere de la medida del radio además de las
coordenadas x,y de su centro.
Esto nos lleva a pensar que para otros objetos tales como: lineas, rectangulos, elipses, etc. ,
pueden derivarse nuevas clases tomando como base la clase pixel y en las cuales se conservan
los miembros dato x,y además de las funciones miembro dax() y day() definidos en la clase punto ;
pero deben definirse nuevas funciones miembro mostrar(), así que, cada vez que se defina una
nueva clase derivada de pixel, debe compilarse un nuevo proyecto ( archivo .PRJ ), donde se
incluyan los archivos PUNTO2.CPP junto con el archivo correspondiente a la nueva figura ( por
ejemplo LINEA.CPP ) para obtener el correspondiente archivo ejecutable ( por ejemplo LINEA.EXE
).
Ya sea que hubieramos escrito las funciones mostrar() de todas las clases de figuras en un solo
archivo fuente, o que las hubieramos manejado en varios archivos de un proyecto; habríamos
tenido que volver a compilar cada vez que una nueva clase hubiera sido añadida.
Suponiendo que ya se tienen las funciones mostrar() para cada uno de los objetos involucrados,
ahora surge la pregunta:
¿ A cuál de las funciónes mostrar() se está haciendo referencia ?
Para responder a esto, con los mecanismos del C++ estudiados hasta antes de esta unidad, se
pueden utilizar las siguientes formas:

1.- La que realiza la distinción por medio del nombre ampliado.


Por ejemplo, mostrar(int,char) no es lo mismo que
mostrar(int,float).

2.- La que utiliza el operador de resolución de ámbito, por medio


del cual circulo::mostrar() se distingue de pixel::mostrar()
y de ::mostrar().

3.- La que distingue de acuerdo a la clase de objeto que se maneja.


Por ejemplo: uncirculo.mostrar() invoca a circulo::mostrar(),
mientras que unpixel.mostrar() invoca a pixel::mostrar().

Todas esas resoluciones de función se han hecho en tiempo de compilación, por medio del
mecanismo conocido como enlace temprano o estático.
La alternativa en C++ se dá a través del polimorfismo, que consiste en la capacidad que posee el
código para comportarse de diversas maneras. Cada forma de comportamiento dependerá de una
situación específica, determinada en tiempo de ejecución, por medio del enlace tardío.
El enlace tardío o dinámico trabaja por medio de funciones miembro especiales llamadas funciones
virtuales.

FUNCIONES VIRTUALES

El concepto clave en las funciones virtuales es que las invocaciones a ellas se resuelven en tiempo
de ejecución (de ahí el término enlace tardío). En nuestro ejemplo, esto significa que la invocación
a la función mostrar() adecuada va a realizarse hasta que se conozca el tipo de objeto involucrado.
Consideremos la función miembro circulo::mover_hacia() definida en CIRCULO.CPP :

void circulo::mover_hacia(int nvax, int nvay)


{
ocultar();
x = nvax;
y = nvay;
mostrar();
}

Observamos esta definición es parecida a la de pixel::mover_hacia() que se encuentra en


PUNTO2.CPP :

void pixel::mover_hacia(int nvax, int nvay)


{
ocultar();
x = nvax;
y = nvay;
mostrar();
}

El valor de retorno, el nombre de la función, el número y tipo de argumentos, y hasta el cuerpo de


la función, son idénticos.
Si el C++ encuentra dos llamadas a función que tengan el mismo nombre de función pero
diferentes nombres ampliados, el compilador resuelve las ambigüedades.
Pero nuestras dos funciones mover_hacia() , a simple vista, no ofrecen al compilador ninguna pista
distintiva. ¿ Cómo va a saber el compilador a cuál función llamar ?. Cuando se trata de funciones
miembro ordinarias, el compilador determina la función objetivo a partir de la clase del objeto
involucrado en la llamada.
Entonces, ¿ porqué no dejar que circulo herede la función mover_hacia() desde pixel, tal como
hereda las funciones miembro dax() y day(), desde la clase punto vía la clase pixel ?.
La razón para no hacerlo es que un pixel se mueve de manera diferente a como se mueve un
círculo y las funciones ocultar() y mostrar() invocadas en circulo::mover_hacia() no son las mismas
que se invocan en pixel::mover_hacia(). Heredar mover_hacia() desde pixel puede llevar a invocar
a las funciones ocultar() y mostrar() erróneas cuando se intente mover un círculo.
La solución a este problema es declarar a las funciones ocultar() y mostrar() como funciones
virtuales. Esto retardará el enlace para que se hagan las llamadas a las versiones correctas de
esas funciones cuando se invoque a la función mover_hacia() ya sea para mover un pixel o para
mover un círculo (o cualquier otra figura ).
DECLARACION DE FUNCIONES VIRTUALES

Para declarar una función como virtual, simplemente hay que anteponer la palabra virtual en la
primera declaración de la función miembro.
Por ejemplo :

virtual void mostrar();


virtual void ocultar();

Es importante recordar que solamente las funciones miembro pueden ser declaradas como virtual ,
y que no deben redeclararse en las clases derivadas.
Si redeclaramos a la función miembro mostrar() con los mismos parámetros y el mismo tipo de
retorno, en una clase derivada, la nueva función mostrar() automáticamente se vuelve virtual, ya
sea que se utilice o no el calificador virtual. Se dice que la nueva función virtual mostrar()
sobreescribe a la función mostrar() de la clase base.
Tenemos la libertad de redeclarar a la función mostrar() con parámetros diferentes ( cambiando o
no el tipo de retorno ), pero entonces el mecanismo virtual será inoperante para esta versión de
mostrar().
Al principio, deberá evitarse la sobrecarga temeraria, ya que hay situaciones en que una función no
virtual puede ocultar a una función virtual en su clase base.
A manera de ejemplo, vamos a crear un módulo ( Por medio de los archivos FIGURAS.H y
FIGURAS.CPP mostrados más adelante ) que define algunas clases de figuras y crea un
mecanismo generalizado para arrastrarlas a través de la pantalla.
El principal objetivo, en el diseño del módulo, es permitir a los usuarios del mismo extender las
clases definidas en él.
Al añadir una nueva función miembro a una jerarquía de clases existente, surge la siguiente
reflexión : ¿ En qué lugar de la jerarquía debe ser colocada la nueva función miembro ?
La función arrastrar() debe ser un miembro de la clase pixel, para que todos los objetos de clases
derivadas de pixel puedan arrastrase a través de la pantalla.
Una vez decidido el lugar donde va a colocarse la función arrastrar(), vamos a rediseñar su
definición.
Vamos a re-escribir la función arrastar() para que las funciones que invoca, tales como: dax(),
day(), mostrar(), mover_hacia() y ocultar() , referencien las versiones correspondientes al tipo de
objeto que va a ser arrastrado.
En la nueva definición de la clase pixel, del archivo FIGURAS.H mostrado en el listado 8.7, se
hacen virtuales a las funciones miembro mostrar() , ocultar() y arrastrar().

////////////////////////////////////////////////////////
// FIGURAS.H : CONTIENE LA DEFINICION DE LAS CLASES //
// punto, pixel y circulo //
// EN UN SISTEMA A COMERCIALIZAR, ESTA SERIA LA //
// UNICA PARTE QUE DEBE DISTRIBUIRSE EN FORMA DE //
// CODIGO FUENTE. //
////////////////////////////////////////////////////////
enum booleano { falso, verdadero } ;
class punto
{
protected:
int x, y ;
public:
punto(int nx, int ny) { x = nx ; y = ny ; }
int dax() { return x ; }
int day() { return y ; }
};

class pixel : public punto


{
protected:
booleano visible ;
public:
pixel(int nx, int ny);
virtual void mostrar();
virtual void ocultar();
virtual void arrastrar(int narrastre);
booleano es_visible() { return visible ; }
void mover_hacia(int nvax, int nvay);
};

class circulo : public pixel


{
protected:
int radio;
public:
circulo(int nx, int ny, int nradio);
void mostrar();
void ocultar();
void expander(int cantexp);
void contraer(int cantcont);
};
// PROTOTIPO PARA LA FUNCION NO MIEMBRO leedelta()
// DEFINIDA EN EL LISTADO 8.8
booleano leedelta(int& deltax, int& deltay);

El archivo FIGURAS.CPP, mostrado en el listado 8.8, contiene las definiciones para las funciones
miembro declaradas en el listado 8.7.

////////////////////////////////////////////////////////////
// FIGURAS.CPP : CONTIENE LAS DEFINICIONES PARA LAS //
// FUNCIONES DE LAS CLASES DECLARADAS EN FIGURAS.H //
// //
// EN UN CONTEXTO COMERCIAL, ESTE ARCHIVO PUEDE DISTRI- //
// BUIRSE EN FORMA DE OBJETO ( .OBJ ). //
////////////////////////////////////////////////////////////
#include <graphics.h>
#include <conio.h>
#include "figuras.h"

// DEFINICION DE LAS FUNCIONES MIEMBRO PARA LA CLASE pixel

pixel::pixel(int nx, int ny) : punto(nx,ny)


{
visible = falso ;
}
void pixel::mostrar()
{
visible = verdadero;
putpixel(x,y,setcolor());
}

void pixel::ocultar()
{
visible = falso;
putpixel(x,y, getbkcolor());
}

void pixel::mover_hacia(int nvax, int nvay)


{
ocultar();
x = nvax ; y = nvay ;
mostrar();
}

void pixel::arrastrar(int narrastre)


{
int deltax, deltay ;
int figx, figy ;
mostrar() ; // DESPLIEGA LA FIGURA A SER ARRASTRADA
figx = dax();
figy = day();

// ESTE ES EL CICLO DE ARRASTRE


while(leedelta(deltax,deltay))
{
figx += (deltax * narrastre) ;
figy += (deltay * narrastre) ;
mover_hacia(figx, figy) ;
};
}
// LA SIGUIENTE ES UNA FUNCION DE PROPOSITOS GENERALES QUE
// UTILIZA LAS TECLAS DE FLECHAS PARA MOVER EL CURSOR.

booleano leedelta(int& deltax, int& deltay)


{
char car;
booleano salir;
deltax = 0 ;
deltay = 0 ;
do{
car = getch() ; // LEE LA TECLA
if(car == 13) // RETORNO DE CARRO
return falso ;
if(car == 0 ) // CODIGO EXTENDIDO
{
salir = verdadero ; // ASUME QUE ES UTILIZABLE
car = getch() ; // TOMA EL RESTO DEL CODIGO
switch(car)
{
case 72 : deltay = -1 ; break ; // ARRIBA
case 80 : deltay = 1 ; break ; // ABAJO
case 75 : deltax = -1 ; break ; // IZQ.
case 77 : deltax = 1 ; break ; // DER.
default : salir = falso ; // OTRA
};
}
} while( !salir ) ;
return verdadero;
}

// DEFINICION DE LAS FUNCIONES MIEMBRO PARA LA CLASE circulo


circulo::circulo(int nx, int ny, int nradio) : pixel(nx, ny)
{
radio = nradio;
}
void circulo::mostrar()
{
visible = verdadero;
circle(x,y,radio);
}

void circulo::ocultar()
{
unsigned int colortemp;
colortemp = getcolor();
setcolor(getbkcolor());
visible = falso;
circle(x,y, radio);
setcolor(colortemp);
}

void circulo::expander(int cantexp)


{
ocultar();
radio += cantexp;
if(radio <0) radio="0;" mostrar(); } void circulo::contraer(int cantcont) { expander(-cantcont); }

Ahora vamos a probar las nuevas clases punto, pixel y circulo utilizándolas para derivar una clase
llamada arco que se encuentra definida en el archivo POLIMORF.CPP del listado 8.9.

////////////////////////////////////////////////////////////
// POLIMORF.CPP : CONTIENE LA DEFINICION DE LA CLASE arco //
// QUE SE DERIVA DE circulo. //
// Para compilar, forme un proyecto con FIGURAS.OBJ y //
// POLIMOFIRSMO //
////////////////////////////////////////////////////////////

#include <graphics.h>
#include <conio.h>
#include "figuras.h"

class arco : public circulo


{
int anginic, angfin ;

public:
arco(int nx, int ny, int nradio,
int nanginic, int nangfin) : circulo(nx, ny, nradio)
{
anginic = nanginic;
angfin = nangfin;
}
void mostrar() ; // ESTAS FUNCIONES SON VIRTUALES EN
// pixel
void ocultar() ;
};

// DEFINICION DE LAS FUNCIONES MIEMBRO PARA LA CLASE arco

void arco::mostrar()
{
visible = verdadero ;
arc( x, y, anginic, angfin, radio ) ;
}

void arco::ocultar()
{
int colortemp;
colortemp = getcolor();
setcolor(getbkcolor());
visible = falso ;
arc(x, y, anginic, angfin, radio);
setcolor(colortemp);
}

// DEFINICION DE LA FUNCION PRINCIPAL

void main()
{
int manej =DETECT, modo ;
initgraph( &manej, &modo, "c:\\borlandc\\bgi");
circulo uncirculo(151,82,50);
arco unarco(151,82,25,0,190);
unarco.arrastrar(10) ; // LA FUNCION arrastar() SE APLICA
// A UNA FIGURA QUE NO CONOCE.
unarco.ocultar();
uncirculo.arrastrar(10);
closegraph();
getch();
}
OBJETOS DINAMICOS

En los ejemplos mostrados anteriormente se utilizaron objetos cuyo espacio de almacenamiento se manejó en
la pila.
En esta sección trataremos con objetos dinámicos, cuyo espacio de almacenamiento en el montículo se
asigna y se libera en tiempo de ejecución por medio de los operadores new y delete.
Por ejemplo, si existe una clase llamada punto, podemos escribir:

punto* ap ;

ap = new punto(10,20);

En la primera línea se está declarando un apuntador llamado ap, que servirá para apuntar a objetos de clase
punto .
En la segunda línea se gestiona un bloque de memoria lo suficientemente grande para almacenar un objeto
de la clase punto, se invoca al constructor punto pasándole los argumentos 10,20 . La dirección de inicio del
bloque gestionado se asigna al apuntador ap.
Las dos líneas anteriores pueden sustituirse por:

punto* ap = new punto(10,20);

En el listado 8.10 se presenta un ejemplo del manejo de dos objetos dinámicos, donde, además, se utiliza el
polimorfismo.
En 8.10 puede observarse que se declara el apuntador ap para que apunte a objetos de clase numero y, sin
embargo, ese mismo apuntador se utiliza para apuntar a objetos de clase division.
Esto se debe a que, en C++, un apuntador declarado para apuntar a objetos de una clase base puede
utilizarse para apuntar a objetos de una clase derivada.
Esta regla no es válida en el sentido inverso; esto es, un apuntador declarado para apuntar a objetos de una
clase derivada no puede utilizarse para apuntar a objetos de la clase base, por lo que:

division* ap;
ap = new numero(10);

produce un error.

///////////////////////////////////////////////////////
// OBJDIN.CPP : Muestra el uso de objetos dinámicos. //
///////////////////////////////////////////////////////

#include <iostream.h>

class numero
protected:
int valor;
public:
numero(int numdado) { valor = numdado;}
virtual int davalor(){return valor;}
class division : public numero
{
protected:
int divisor;
public:
division(int d, int n) : numero(d) {divisor=n;}
virtual int davalor() {return valor/divisor;}
void main()
{
numero* ap;
ap = new numero(15);

cout << ap->davalor() << '\n'; //Despliega 15 delete ap; ap="new" division(15,2); cout << ap->davalor() <<
'\n'; // Despliega 7 }

Potrebbero piacerti anche