Está en la página 1de 132

Introducción a la Programación y Computación 1

Cuarta Unidad:
Programación
Orientada a Objetos
(POO)
Introducción
La Programación Orientada a Objetos (POO) es un enfoque conceptual específico para diseñar
programas, utilizando un lenguaje de programación orientado a objetos, en nuestro caso C++ o Java.
Las propiedades más importantes de la POO son:
• Abstracción.
• Encapsulamiento y ocultación de datos.
• Polimorfismo.
• Herencia.
• Reusabilidad o reutilización de código.

Este paradigma de programación viene a superar las limitaciones que soporta la programación
tradicional o "procedimental".
Los elementos fundamentales de la POO son las clases y objetos. En esencia, la POO se concentra en
el objeto tal como lo percibe el usuario, pensando en los datos que se necesitan para describir el
objeto y las operaciones que describirán la iteración del usuario con los datos. Después se desarrolla
una descripción de la interfaz externa y se decide cómo implementar la interfaz y el almacenamiento
de datos. Por último, se ponen juntos en un programa que utilice su nuevo dueño.
Modelado de objetos del mundo real
Una limitación de la programación estructurada reside en el hecho de que la separación de los
datos y las funciones que manipulan esos datos proporcionan un modelo muy pobre de las cosas
y objetos del mundo real. En el mundo real se trata con objetos tales como personas, casas o
motocicletas, y tienen a su vez incorporados atributos (datos) y comportamiento (funciones).
Los objetos —complejos o no complejos— del mundo real tienen atributos y comportamiento.
Los atributos o características son las propiedades de los objetos; por ejemplo, para las
personas, la estatura, el color del cabello y de los ojos, la edad, etc.; para un automóvil (coche o
carro), la marca, la potencia, el número de puertas, el precio, etc. Los atributos del mundo real
son equivalentes a los datos de un programa y tienen un valor determinado, 200 metros
cuadrados, 20.000 dólares, cinco puertas, etc.
El comportamiento es la acción que realizan los objetos del mundo real en respuesta a un
determinado estímulo. Por ejemplo, si se acelera un carro, aumenta su velocidad; si se frena un
carro se ralentiza o para. El comportamiento es similar a una función; la llamada a una función
para realizar una tarea determinada, por ejemplo dibujar un rectángulo o visualizar la nómina de
los empleados de una empresa.
Programación Orientada a Objetos
Al contrario que el enfoque procedimental que se basa en la interrogante ¿qué hace este
programa?, el enfoque orientado a objetos responde a otro interrogante ¿qué objetos del
mundo real puede modelar?
La POO (Programación Orientada a Objetos) se basa en el hecho de que se debe dividir el
programa, no en tareas, sino en modelos de objetos físicos o simulados. Aunque esta idea
parece abstracta a primera vista, se vuelve más clara cuando se consideran objetos físicos
en términos de sus clases, componentes, propiedades y comportamiento, y sus objetos
instanciados o creados de las clases.
Si se escribe un programa de computadora en un lenguaje orientado a objetos, se está
creando, en su computadora, un modelo de alguna parte del mundo. Las partes que el
modelo construye son los objetos que aparecen en el dominio del problema. Estos
objetos deben ser representados en el modelo que se está creando en la computadora.
Los objetos se pueden agrupar en categorías, y una clase describe —de un modo
abstracto— todos los objetos de un tipo o categoría determinada.
Programación Orientada a Objetos
La idea fundamental de la orientación a objetos y de los lenguajes que implementan este
paradigma de programación es combinar (encapsular) en una única unidad tanto los
datos como las funciones que operan (manipulan) sobre los datos. Esta característica
permite modelar los objetos del mundo real de un modo mucho más eficiente que con
funciones y datos. Esta unidad de programación se denomina objeto.
En un sistema orientado a objetos, un programa se organiza en un conjunto finito de
objetos que contienen datos y operaciones que se comunican entre sí mediante
mensajes.

.
Tipos de Datos Abstractos
(Clases)
Tipos de Datos Abstractos (Clases)
Un progreso importante en la historia de los lenguajes de programación se produjo cuando se
comenzó a combinar juntos diferentes elementos de datos y, por consiguiente, encapsular o
empaquetar diferentes propiedades en un tipo de dato. Estos tipos fueron las estructuras o registros
que permiten a una variable contener datos que pertenecen a las circunstancias representadas por
ellas.
Las estructuras representan un modo de abstracción con los programas, concretamente la
combinación (o composición) de partes diferentes o elementos (miembros). Así, por ejemplo, una
estructura coche constará de miembros tales como marca, motor, número de matrícula, año de
fabricación, etc.
Sin embargo, aunque en las estructuras y registros se pueden almacenar las propiedades individuales
de los objetos en los miembros, en la práctica cómo están organizados, no pueden representar qué se
puede hacer con ellos (moverse, acelerar, frenar, etc. en el caso de un coche/carro). Se necesita que
las operaciones que forman la interfaz de un objeto se incorporen también al objeto.
El tipo abstracto de datos (TAD) describe, no sólo los atributos de un objeto sino también su
comportamiento (operaciones o funciones) y, en consecuencia, se puede incluir una descripción de
los estados que puede tener el objeto.
Instancias
Una clase describe un objeto, en la práctica múltiples objetos. En conceptos de
programación, una clase es, realmente, un tipo de dato, y se pueden crear, en
consecuencia, variables de ese tipo. En programación orientada a objetos, a estas
variables, se las denomina instancias (“instances”), y también por sus sinónimos
ejemplares, casos, etcétera.
Las instancias son la implementación de los objetos descritos en una clase. Estas
instancias constan de los datos o atributos descritos en la clase y se pueden
manipular con las operaciones definidas en la propia clase.
En un lenguaje de programación OO, objeto e instancia son términos sinónimos.
Así, cuando se declara una variable de tipo Auto, se crea un objeto Auto (una
instancia de la clase Auto).
Clases
Una clase es una plantilla para la creación de objetos de datos según un modelo predefinido. Las
clases se utilizan para representar entidades o conceptos, como los sustantivos en el lenguaje. Cada
clase es un modelo que define un conjunto de variables -el estado, y métodos apropiados para operar
con dichos datos -el comportamiento. Cada objeto creado a partir de la clase se denomina instancia
de la clase. Permiten abstraer los datos y sus operaciones asociadas al modo de una caja negra.

Las clases se componen de elementos, llamados genéricamente “miembros”, de varios tipos:

• atributos: almacenan el estado de la clase por medio de variables, estructuras de datos e incluso
otras clases.

• métodos: subrutinas de manipulación de dichos datos.

• ciertos lenguajes permiten un tercer tipo de miembro: las «propiedades», a medio camino entre
los campos y los métodos.

Utilizando un símil con el lenguaje, si las clases representan sustantivos, los campos de datos pueden
ser sustantivos o adjetivos, y los métodos son los verbos.
Método
Algoritmo asociado a un objeto (o a una clase de objetos), cuya ejecución se
desencadena tras la recepción de un "mensaje". Desde el punto de vista del
comportamiento, es lo que el objeto puede hacer. Un método puede producir un
cambio en las propiedades del objeto, o la generación de un "evento" con un nuevo
mensaje para otro objeto del sistema.
La diferencia entre un procedimiento (generalmente llamado función si devuelve
un valor) y un método es que este último, al estar asociado con un objeto o clase en
particular, puede acceder y modificar los datos privados del objeto correspondiente
de forma tal que sea consistente con el comportamiento deseado para el mismo.
Así, es recomendable entender a un método no como una secuencia de
instrucciones sino como la forma en que el objeto es útil (el método para hacer su
trabajo). Por lo tanto, podemos considerar al método como el pedido a un objeto
para que realice una tarea determinada o como la vía para enviar un mensaje al
objeto y que éste reaccione acorde a dicho mensaje
Atributos y Propiedades
Atributos
Los Atributos se utilizan para contener datos que reflejan el estado de la clase. Los
datos pueden estar almacenados en variables, o estructuras más complejas, como
arreglos e incluso otras clases.
Habitualmente, las variables miembro son privadas al objeto (siguiendo las
directrices de diseño del Principio de ocultación) y su acceso se realiza mediante
propiedades o métodos que realizan comprobaciones adicionales.
Propiedades
Las propiedades son los atributos de la computadora. Debido a que suele ser común
que las variables miembro sean privadas para controlar el acceso y mantener la
coherencia, surge la necesidad de permitir consultar o modificar su valor mediante
pares de métodos: GetVariable y SetVariable.
Objetos
Un objeto es una unidad dentro de un programa de computadores que consta de un
estado y de un comportamiento, que a su vez constan respectivamente de datos
almacenados y de tareas realizables durante el tiempo de ejecución. Un objeto
puede ser creado instanciando una clase.
Estos objetos interactúan unos con otros, en contraposición a la visión tradicional
en la cual un programa es una colección de subrutinas (funciones o
procedimientos), o simplemente una lista de instrucciones para el computador.
Cada objeto es capaz de recibir mensajes, procesar datos y enviar mensajes a otros
objetos de manera similar a un servicio.
Mensaje
Una comunicación dirigida a un objeto, que le ordena que ejecute uno de sus
métodos con ciertos parámetros asociados al evento que lo generó.
El paso de mensajes es una técnica que se usa para, desde un proceso, invocar de
forma abstracta un comportamiento concreto por parte de otro actor (Por ejemplo,
ejecutar una función o un programa). El emisor envía un mensaje solicitando una
acción abstracta al destinatario, y es el destinatario el que, al recibir el mensaje,
decidirá la acción que debe ejecutar para cumplir la petición.
Evento
Es un suceso en el sistema (tal como una interacción del usuario con la máquina, o
un mensaje enviado por un objeto). El sistema maneja el evento enviando el
mensaje adecuado al objeto pertinente. También se puede definir como evento la
reacción que puede desencadenar un objeto; es decir, la acción que genera.
Ejemplo
Nuestro primer ejemplo consiste en dos clases: Tiempo1 y PruebaTiempo1. La clase
Tiempo1 representa la hora del día. La clase PruebaTiempo1 es una clase de
aplicación en la que el método main crea un objeto de la clase Tiempo1 e invoca a
sus métodos. Estas clases se deben declarar en filas separadas ya que ambas son de
tipo public.
La clase Tiempo1 contiene tres variables de instancia private de tipo int (líneas 6 a
8): hora, minuto y segundo, que representan la hora en formato de tiempo universal
(formato de reloj de 24 horas, en el cual las horas se encuentran en el rango de 0 a
23). La clase Tiempo1 contiene los métodos public establecerTiempo (líneas 12 a
17), aStringUniversal (líneas 20 a 23) y toString (líneas 26 a 31). A estos métodos
también se les llama servicios public o la interfaz public que proporciona la clase a
sus clientes.
Ejemplo
Ejemplo
En este ejemplo, la clase Tiempo1 no declara un constructor, por lo que tiene un constructor predeterminado que
le suministra el compilador. Cada variable de instancia recibe en forma implícita el valor predeterminado 0 para
un int. Observe que las variables de instancia también pueden inicializarse cuando se declaran en el cuerpo de la
clase, usando la misma sintaxis de inicialización que la de una variable local.

El método establecerTiempo (líneas 12 a 17) es un método public que declara tres


parámetros int y los utiliza para establecer la hora. Una expresión condicional evalúa cada
argumento, para determinar si el valor se encuentra en un rango especificado. Por ejemplo,
el valor de hora (línea 14) debe ser mayor o igual que 0 y menor que 24, ya que el formato
de hora universal representa las horas como enteros de 0 a 23 (por ejemplo, la 1 PM es la
hora 13 y las 11 PM son la hora 23; medianoche es la hora 0 y mediodía es la hora 12). De
manera similar, los valores de minuto y segundo (líneas 15 y 16) deben ser mayores o
iguales que 0 y menores que 60. Cualquier valor fuera de estos rangos se establece como
cero para asegurar que un objeto Tiempo1 siempre contenga datos consistentes; esto es, los
valores de datos del objeto siempre se mantienen en rango, aun si los valores que se
proporcionan como argumentos para el método establecerTiempo son incorrectos. En este
ejemplo, cero es un valor consistente para hora, minuto y segundo.
Ejemplo
Un valor que se pasa a establecerTiempo es correcto si se encuentra dentro del rango permitido para el
miembro que va a inicializar. Por lo tanto, cualquier número en el rango de 0 a 23 sería un valor
correcto para la hora. Un valor correcto siempre es un valor consistente. Sin embargo, un valor
consistente no es necesariamente un valor correcto. Si establecerTiempo establece hora a 0 debido a
que el argumento que recibió se encontraba fuera del rango, entonces establecerTiempo está recibiendo
un valor incorrecto y lo hace consistente, para que el objeto permanezca en un estado consistente en
todo momento. La hora correcta del día podrían ser las 11 AM, pero debido a que la persona pudo haber
introducido en forma accidental una hora fuera de rango (incorrecta), optamos por establecer la hora al
valor consistente de cero. En este caso, tal vez sea conveniente indicar que el objeto es incorrecto.
El método aStringUniversal (líneas 20 a 23) no recibe argumentos y devuelve un objeto String en
formato de hora universal, el cual consiste de seis dígitos: dos para la hora, dos para los minutos y dos
para los segundos. Por ejemplo, si la hora es 1:30:07 PM, el método aStringUniversal devuelve
13:30:07. La instrucción return (línea 22) utiliza el método static format de la clase String para devolver
un objeto String que contiene los valores con formato de hora, minuto y segundo, cada uno con dos
dígitos y posiblemente, un 0 a la izquierda (el cual se especifica con la bandera 0). El método format es
similar al método System.out.printf, sólo que format devuelve un objeto String con formato, en vez de
mostrarlo en una ventana de comandos. El método aStringUniversal devuelve el objeto String con
formato.
Ejemplo
El método toString (líneas 26 a 31) no recibe argumentos y devuelve un objeto String en
formato de hora estándar, el cual consiste en los valores de hora, minuto y segundo
separados por signos de dos puntos (:), y seguidos de un indicador AM o PM (por ejemplo,
1:27:06 PM). Al igual que el método aStringUniversal, el método toString utiliza el método
static String format para dar formato a los valores de minuto y segundo como valores de dos
dígitos con 0s a la izquierda, en caso de ser necesario. La línea 29 utiliza un operador
condicional (?:) para determinar el valor de hora en la cadena; si hora es 0 o 12 (AM o PM),
aparece como 12; en cualquier otro caso, aparece como un valor de 1 a 11. El operador
condicional en la línea 30 determina si se devolverá AM o PM como parte del objeto String.
La clase de la aplicación PruebaTiempo1 utiliza la clase Tiempo1. La línea 9 declara y crea
un objeto Tiempo1 y lo asigna a la variable local tiempo. Observe que new invoca en forma
implícita al constructor predeterminado de la clase Tiempo1, ya que Tiempo1 no declara
constructores. Las líneas 12 a 16 imprimen en pantalla la hora, primero en formato
universal (mediante la invocación al método aStringUniversal en la línea 13) y después en
formato estándar (mediante la invocación explícita del método toString de tiempo en la
línea 15) para confirmar que el objeto Tiempo1 se haya inicializado en forma apropiada.
Ejemplo
Ejemplo
La línea 19 invoca al método establecerTiempo del objeto tiempo para modificar la
hora. Después las líneas 20 a 24 imprimen en pantalla la hora otra vez en ambos
formatos, para confirmar que la hora se haya ajustado en forma apropiada.
Para ilustrar que el método establecerTiempo mantiene el objeto en un estado
consistente, la línea 27 llama al método establecerTiempo con los argumentos de 99
para la hora, el minuto y el segundo. Las líneas 28 a 32 imprimen de nuevo el
tiempo en ambos formatos, para confirmar que establecerTiempo haya mantenido
el estado consistente del objeto, y después el programa termina. Las últimas dos
líneas de la salida de la aplicación muestran que el tiempo se restablece a
medianoche (el valor inicial de un objeto Tiempo1) si tratamos de establecer el
tiempo con tres valores fuera de rango.
Datos estáticos y dinámicos
Cuando el sistema operativo carga un programa para ejecutarlo y lo convierte en
proceso, le asigna cuatro partes lógicas en memoria principal: texto, datos
(estáticos), pila y una zona libre. Esta zona libre (o heap) es la que va a contener los
datos dinámicos, la cual, a su vez, en cada instante de la ejecución tendrá partes
asignadas a los mismos y partes libres que fragmentarán esta zona, siendo posible
que se agote si no se liberan las partes utilizadas ya inservibles. (La pila también
varía su tamaño dinámicamente, pero la gestiona el sistema operativo, no el
programador):
Para trabajar con datos dinámicos necesitamos dos cosas:
1. Subprogramas predefinidos en el lenguaje que nos permitan gestionar la
memoria de forma dinámica (asignación y liberación).
2. Algún tipo de dato con el que podamos acceder a esos datos dinámicos.
Datos estáticos y dinámicos
main y código de funciones
empeladas
Variables globales o de
tipo static

Variables dinámicas del


programa

Variables y datos de las funciones


al estar en ejecución
Creación de objetos en Java
La forma típica de crear un objeto en Java es:
NombreClase nombreObjeto = new nombreClase ();
Al hacerlo, la maquina virtual realiza dos tareas:
1. Reserva una porción de memoria en el Heap en función de la clase a la que
pertenece el objeto.
2. Crea una referencia, un puntero, en la pila dentro del marco de referencia que
origina el nuevo objeto.
Creación de objetos en Java
Modelado e identificación
de Objetos
Modelado e identificación de Objetos
Un objeto en software es una entidad individual de un sistema que guarda una relación directa con los
objetos del mundo real. La correspondencia entre objetos de programación y objetos del mundo real es
el resultado práctico de combinar atributos y operaciones, o datos y funciones. Un objeto tiene un
estado, un comportamiento y una identidad.
Estado
Conjunto de valores de todos los atributos de un objeto en un instante de tiempo determinado. El
estado de un objeto viene determinado por los valores que toman sus datos o atributos. Estos valores
han de cumplir siempre las restricciones (invariantes de clase, para objetos pertenecientes a la misma
clase) que se hayan impuesto. El estado de un objeto tiene un carácter dinámico que evoluciona con el
tiempo, con independencia de que ciertos elementos del objeto puedan permanecer constantes.
Comportamiento
Conjunto de operaciones que se pueden realizar sobre un objeto. Las operaciones pueden ser de
observación del estado interno del objeto, o bien de modificación de dicho estado. El estado de un
objeto puede evolucionar en función de la aplicación de sus operaciones. Estas operaciones se realizan
tras la recepción de un mensaje o estímulo externo enviado por otro objeto. Las interacciones entre los
objetos se representan mediante diagramas de objetos. En UML se representarán por enlaces en ambas
direcciones.
Modelado e identificación de Objetos
Identidad
Permite diferenciar los objetos de modo no ambiguo independientemente de su estado. Es posible
distinguir dos objetos en los cuáles todos sus atributos sean iguales. Cada objeto posee su propia
identidad de manera implícita. Cada objeto ocupa su propia posición en la memoria de la
computadora.

En un sistema orientado a objetos los programas se organizan en conjuntos finitos que contienen
atributos (datos) y operaciones (funciones) y que se comunican entre sí mediante mensajes. Los pasos
típicos en el modelado de un sistema orientado a objetos son:
1. Identificar los objetos que forman parte del modelo.
2. Agrupar en clases todos aquellos objetos que tengan características y comportamientos comunes.
3. Identificar los atributos y las operaciones de cada clase.
4. Identificar las relaciones existentes entre las clases.
Propiedades POO
Composición
Una clase puede tener referencias a objetos de otras clases como miembros. A dicha capacidad se le
conoce como composición y algunas veces como relación “tiene un”. Por ejemplo, un objeto de la
clase RelojAlarma necesita saber la hora actual y la hora en la que se supone sonará su alarma, por lo
que es razonable incluir dos referencias a objetos Tiempo como miembros del objeto RelojAlarma.

Nuestro ejemplo de composición contiene tres clases: Fecha, Empleado y PruebaEmpleado. La clase
Fecha declara las variables de instancia mes, dia y anio (líneas 6 a 8) para representar una fecha. El
constructor recibe tres parámetros int. La línea 14 invoca el método utilitario comprobarMes (líneas
23 a 33) para validar el mes; un valor fuera de rango se establece en 1 para mantener un estado
consistente. La línea 15 asume que el valor de anio es correcto y no lo valida. La línea 16 invoca al
método utilitario comprobarDia (líneas 36 a 52) para validar el valor de dia con base en el mes y
anio actuales. Las líneas 42 y 43 determinan si el día es correcto, con base en el número de días en el
mes específico. Si el día no es correcto, las líneas 46 y 47 determinan si el mes es Febrero, el día 29 y
el anio un año bisiesto. Si las líneas 42 a 48 no devuelven un valor correcto para dia, la línea 51
devuelve 1 para mantener la Fecha en un estado consistente. Observe que las líneas 18 y 19 en el
constructor muestran en pantalla la referencia this como un objeto String. Como this es una
referencia al objeto Fecha actual, se hace una llamada implícita al método toString (líneas 55 a 58)
para obtener la representación String del objeto.
Composición
Composición

La clase Empleado tiene las variables de instancia primerNombre, apellidoPaterno, fechaNacimiento y


fechaContratacion. Los miembros fechaNacimiento y fechaContratacion (líneas 8 y 9) son referencias a
objetos Fecha. Esto demuestra que una clase puede tener como variables de instancia referencias a
objetos de otras clases. El constructor de Empleado (líneas 12 a 19) recibe cuatro parámetros: nombre,
apellido, fechaDeNacimiento y fechaDeContratacion. Los objetos referenciados por los parámetros
fechaDeNacimiento y fechaDeContratacion se asignan a las variables de instancia fechaNacimiento y
fechaContratacion del objeto Empleado, respectivamente. Observe que cuando se hace una llamada al
método toString de la clase Empleado, éste devuelve un objeto String que contiene las representaciones
String de los dos objetos Fecha. Cada uno de estos objetos String se obtiene mediante una llamada
implícita al método toString de la clase Fecha.
Composición

La clase PruebaEmpleado crea dos objetos Fecha (líneas 8 y 9) para representar la fecha
de nacimiento y de contratación de un Empleado, respectivamente. La línea 10 crea un
Empleado e inicializa sus variables de instancia, pasando al constructor dos objetos
String (que representan el nombre y el apellido del Empleado) y dos objetos Fecha (que
representan la fecha de nacimiento y de contratación). La línea 12 invoca en forma
implícita el método toString de Empleado para mostrar en pantalla los valores de sus
variables de instancia y demostrar que el objeto se inicializó en forma apropiada.

Fecha Empleado
-int mes -String primerNombre
-int dia -String apellidoPaterno
-int anio -Fecha fechaNacimiento
-Fecha(int elMes, int elDia, int elAnio) -Fecha fechaContratacion
-int comprobarMes(int mesPrueba) -Empleado(String nombre, String
-int comprobarDia(int diaPrueba) apellido, Fecha fechaDeNacimiento,
-String toString() Fecha fechaDeContratacion)
-String toString()
Abstracción
Denota las características esenciales de un objeto, donde se capturan sus
comportamientos. Cada objeto en el sistema sirve como modelo de un "agente"
abstracto que puede realizar trabajo, informar y cambiar su estado, y "comunicarse"
con otros objetos en el sistema sin revelar cómo se implementan estas
características. Los procesos, las funciones o los métodos pueden también ser
abstraídos, y, cuando lo están, una variedad de técnicas son requeridas para
ampliar una abstracción. El proceso de abstracción permite seleccionar las
características relevantes dentro de un conjunto e identificar comportamientos
comunes para definir nuevos tipos de entidades en el mundo real. La abstracción es
clave en el proceso de análisis y diseño orientado a objetos, ya que mediante ella
podemos llegar a armar un conjunto de clases que permitan modelar la realidad o el
problema que se quiere atacar.
Abstracción
Por ejemplo las pilas pueden implementarse mediante arreglos y con otras
estructuras de datos, como las listas enlazadas. El cliente de una clase pila no
necesita preocuparse por la implementación de la pila. El cliente sólo sabe que
cuando se colocan elementos de datos en la pila, éstos se recuperarán en el orden
del último en entrar, primero en salir. El cliente se preocupa acerca de qué
funcionalidad ofrece una pila, pero no acerca de cómo se implementa esa
funcionalidad. A este concepto se le conoce como abstracción de datos. Aunque
los programadores pudieran conocer los detalles de la implementación de una clase,
no deben escribir código que dependa de esos detalles. Esto permite que una clase
en particular (como una que implemente a una pila y sus operaciones: meter y
sacar) se reemplace con otra versión, sin afectar al resto del sistema. Mientras que
los servicios public de la clase no cambien (es decir, que cada método original tenga
aún el mismo nombre, tipo de valor de retorno y lista de parámetros en la
declaración de la nueva clase), el resto del sistema no se ve afectado.
Encapsulamiento
Significa reunir todos los elementos que pueden considerarse pertenecientes a una
misma entidad, al mismo nivel de abstracción. Esto permite aumentar la cohesión
de los componentes del sistema.
Cada objeto está aislado del exterior. El aislamiento protege a los datos asociados
de un objeto contra su modificación por quien no tenga derecho a acceder a ellos,
eliminando efectos secundarios e interacciones.
Encapsulamiento
Encapsulamiento
Herencia
La herencia, que es una forma de reutilización de software en la que se crea una nueva
clase absorbiendo los miembros de una clase existente, y se mejoran con nuevas
capacidades, o con modificaciones en las capacidades ya existentes. Con la herencia, los
programadores ahorran tiempo durante el desarrollo, al reutilizar software probado y
depurado de alta calidad. Esto también aumenta la probabilidad de que un sistema se
implemente con efectividad.
Al crear una clase, en vez de declarar miembros completamente nuevos, el programador
puede designar que la nueva clase herede los miembros de una clase existente. Esta clase
existente se conoce como superclase, y la nueva clase se conoce como subclase. Cada
subclase puede convertirse en la superclase de futuras subclases.
Una subclase generalmente agrega sus propios campos y métodos. Por lo tanto, una
subclase es más específica que su superclase y representa a un grupo más especializado de
objetos. Generalmente, la subclase exhibe los comportamientos de su superclase junto con
comportamientos adicionales específicos de esta subclase. Es por ello que a la herencia se
le conoce algunas veces como especialización.
Herencia
La superclase directa es la superclase a partir de la cual la subclase hereda en forma
explícita. Una superclase indirecta es cualquier clase arriba de la superclase directa
en la jerarquía de clases, la cual define las relaciones de herencia entre las clases. En
Java, la jerarquía de clases empieza con la clase Object (en el paquete java.lang), a
partir de la cual se extienden (o “heredan”) todas las clases en Java, ya sea en forma
directa o indirecta.
Es necesario hacer una diferencia entre la relación “es un” y la relación “tiene un”.
La relación “es un” representa a la herencia. En este tipo de relación, un objeto de
una subclase puede tratarse también como un objeto de su superclase. Por ejemplo,
un automóvil es un vehículo. En contraste, la relación “tiene un” identifica a la
composición. En este tipo de relación, un objeto contiene referencias a objetos
como miembros. Por ejemplo, un automóvil tiene un volante de dirección (y un
objeto automóvil tiene una referencia a un objeto volante de dirección).
Herencia
Como todo objeto de una subclase “es un” objeto de su superclase, y como una
superclase puede tener muchas subclases, el conjunto de objetos representados por
una superclase generalmente es más grande que el conjunto de objetos
representado por cualquiera de sus subclases. Por ejemplo, la superclase Vehiculo
representa a todos los vehículos, incluyendo automóviles, camiones, barcos,
bicicletas, etcétera. En contraste, la subclase Auto representa a un subconjunto más
pequeño y específico de los vehículos.
Las relaciones de herencia forman estructuras jerárquicas en forma de árbol. Una
superclase existe en una relación jerárquica con sus subclases. Cuando las clases
participan en relaciones de herencia, se “afilian” con otras clases. Una clase se
convierte ya sea en una superclase, proporcionando miembros a otras clases, o en
una subclase, heredando sus miembros de otras clases. En algunos casos, una clase
es tanto superclase como subclase.
Creación de Subclases (Herencia)
Se ha visto la creación de una clase Employee. Suponga ahora que desea crear una
clase Manager.
Creación de Subclases (Herencia)
Creación de Subclases (Herencia)
Creación de Subclases (Herencia)
Usaremos una jerarquía de herencia que contiene tipos de empleados en la
aplicación de nómina de una compañía, para hablar sobre la relación entre una
superclase y su subclase. En esta compañía, a los empleados por comisión (que se
representarán como objetos de una superclase) se les paga un porcentaje de sus
ventas, mientras que los empleados por comisión con salario base (que se
representarán como objetos de una subclase) reciben un salario base, más un
porcentaje de sus ventas.
Dividiremos nuestra discusión sobre la relación entre los empleados por comisión y
los empleados por comisión con salario base en cinco ejemplos. El primero declara
la clase EmpleadoPorComision, la cual hereda directamente de la clase Object y
declara como variables de instancia private el primer nombre, el apellido paterno, el
número de seguro social, la tarifa de comisión y el monto de ventas en bruto (es
decir, total).
Creación de Subclases (Herencia)
El segundo ejemplo declara la clase EmpleadoBaseMasComision, la cual también
hereda directamente de la clase Object y declara como variables de instancia private
el primer nombre, el apellido paterno, el número de seguro social, la tarifa de
comisión, el monto de ventas en bruto y el salario base. Para crear esta última clase,
escribiremos cada línea de código que ésta requiera; pronto veremos que es mucho
más eficiente crear esta clase haciendo que herede de la clase
EmpleadoPorComision.
El tercer ejemplo declara una clase EmpleadoBaseMasComision2 separada, la cual
extiende a la clase EmpleadoPorComision (es decir, un
EmpleadoBasePorComision2 es un EmpleadoPorComision que también tiene un
salario base) y trata de acceder a los miembros private de la clase
EmpleadoPorComision; esto produce errores de compilación, ya que la subclase no
puede acceder a las variables de instancia private de la superclase.
Creación de Subclases (Herencia)
El cuarto ejemplo muestra que si las variables de instancia de EmpleadoPorComision se
declaran como protected, una clase EmpleadoBaseMasComision3 que extiende a la
clase EmpleadoPorComision2 puede acceder a los datos de manera directa. Para este
fin, declaramos la clase EmpleadoPorComision2 con variables de instancia protected.
Todas las clases EmpleadoBaseMasComision contienen una funcionalidad idéntica,
pero le mostraremos que la clase EmpleadoBaseMasComision3 es más fácil de crear y
de manipular.
Una vez que hablemos sobre la conveniencia de utilizar variables de instancia protected,
crearemos el quinto ejemplo, el cual establece las variables de instancia de
EmpleadoPorComision de vuelta a private en la clase EmpleadoPorComision3, para
hacer cumplir la buena ingeniería de software. Después le mostraremos cómo una clase
EmpleadoBaseMasComision4 separada, que extiende a la clase
EmpleadoPorComision3, puede utilizar los métodos public de EmpleadoPorComision3
para manipular las variables de instancia private de EmpleadoPorComision3.
Creación de Subclases (Herencia)
Creación de Subclases (Herencia)
Creación y uso de una clase EmpleadoPorComision
Creación de Subclases (Herencia)
Creación y uso de una clase EmpleadoPorComision
Creación de Subclases (Herencia)
Creación y uso de una clase EmpleadoPorComision
Creación de Subclases (Herencia)
Creación de una clase EmpleadoBaseMasComision sin usar la herencia

Ahora hablaremos sobre la segunda parte de nuestra introducción a la herencia,


mediante la declaración y prueba de la clase (completamente nueva e independiente)
EmpleadoBaseMasComision, la cual contiene los siguientes datos: primer nombre,
apellido paterno, número de seguro social, monto de ventas brutas, tarifa de comisión y
salario base. Observe la similitud entre esta clase y la clase EmpleadoPorComision; en
este ejemplo, no explotaremos todavía esa similitud.
Creación de Subclases (Herencia)
Creación de una clase EmpleadoBaseMasComision sin usar la herencia
Creación de Subclases (Herencia)
Creación de una clase EmpleadoBaseMasComision sin usar la herencia
Creación de Subclases (Herencia)
Creación de una clase EmpleadoBaseMasComision sin usar la herencia
Creación de Subclases (Herencia)
Creación de una clase EmpleadoBaseMasComision sin usar la herencia

Literalmente hablando, copiamos el código de la clase EmpleadoPorComision y lo


pegamos en la clase EmpleadoBaseMasComision, después modificamos esta clase
para incluir un salario base y los métodos que manipulan ese salario base. A
menudo, este método de “copiar y pegar” está propenso a errores y consume mucho
tiempo. Peor aún, se pueden esparcir muchas copias físicas del mismo código a lo
largo de un sistema, con lo que el mantenimiento del código se convierte en una
pesadilla. ¿Existe alguna manera de “absorber” las variables de instancia y los
métodos de una clase, de manera que formen parte de otras clases sin tener que
copiar el código? En los siguientes ejemplos responderemos a esta pregunta,
utilizando un método más elegante para crear clases, que enfatiza los beneficios de
la herencia.
Creación de Subclases (Herencia)
Creación de una jerarquía de herencia
EmpleadoPorComision-EmpleadoBaseMasComision

Ahora declararemos la clase EmpleadoBaseMasComision2, que extiende a la clase


EmpleadoPorComision. Un objeto EmpleadoBaseMasComision2 es un
EmpleadoPorComision (ya que la herencia traspasa las capacidades de la clase
EmpleadoPorComision), pero la clase EmpleadoBaseMasComision2 también tiene la variable
de instancia salarioBase (línea 6). La palabra clave extends en la línea 4 de la declaración de
la clase indica la herencia. Como subclase, EmpleadoBaseMasComision2 hereda las variables
de instancia public y protected y los métodos de la clase EmpleadoPorComision.
Creación de Subclases (Herencia)
Creación de una jerarquía de herencia
EmpleadoPorComision-EmpleadoBaseMasComision
Creación de Subclases (Herencia)
La jerarquía de herencia EmpleadoPorComision-EmpleadoBaseMasComision
mediante el uso de variablesde instancia protected

Para permitir que la clase


EmpleadoBaseMasComision acceda
directamente a las variables de instancia
primerNombre, apellidoPaterno,
numeroSeguroSocial, ventasBrutas y
tarifaComision de la superclase, podemos
declarar esos miembros como protected
en la superclase. Los miembros protected
de una superclase se heredan por todas
las subclases de esa superclase.
Creación de Subclases (Herencia)
La jerarquía de herencia EmpleadoPorComision-EmpleadoBaseMasComision
mediante el uso de variablesde instancia protected
Creación de Subclases (Herencia)
La jerarquía de herencia EmpleadoPorComision-EmpleadoBaseMasComision
mediante el uso de variablesde instancia protected
Creación de Subclases (Herencia)
La jerarquía de herencia EmpleadoPorComision-EmpleadoBaseMasComision
mediante el uso de variablesde instancia protected
Creación de Subclases (Herencia)
La jerarquía de herencia EmpleadoPorComision-EmpleadoBaseMasComision
mediante el uso de variablesde instancia protected

En este ejemplo declaramos las variables de instancia de la


superclase como protected, para que las subclases pudieran
heredarlas. Al heredar variables de instancia protected se
incrementa un poco el rendimiento, ya que podemos acceder
directamente a las variables en la subclase, sin incurrir en la
sobrecarga de una llamada a un método establecer u obtener. No
obstante, en la mayoría de los casos es mejor utilizar variables de
instancia private, para cumplir con la ingeniería de software
apropiada, y dejar al compilador las cuestiones relacionadas con la
optimización de código. Su código será más fácil de mantener,
modificar y depurar.
El uso de variables de instancia protected crea varios problemas potenciales. En primer lugar, el objeto de la
subclase puede establecer el valor de una variable heredada directamente, sin utilizar un método establecer.
Por lo tanto, un objeto de la subclase puede asignar un valor inválido a la variable, con lo cual el objeto queda
en un estado inconsistente. Por ejemplo, si declaramos la variable de instancia ventasBrutas de
EmpleadoPorComision3 como protected, un objeto de una subclase (por ejemplo,
EmpleadoBaseMasComision) podría entonces asignar un valor negativo a ventasBrutas. El segundo problema
con el uso de variables de instancia protected es que hay más probabilidad de que los métodos de la subclase se
escriban de manera que dependan de la implementación de datos de la superclase. En la práctica, las subclases
sólo deben depender de los servicios de la superclase y no en la implementación de datos de la superclase.
Creación de Subclases (Herencia)
La jerarquía de herencia EmpleadoPorComision-EmpleadoBaseMasComision
mediante el uso de variables de instancia private

Ahora reexaminaremos nuestra jerarquía una vez más, pero ahora utilizaremos las mejores
prácticas de ingeniería de software. La clase EmpleadoPorComision3 declara las variables de
instancia primerNombre, apellidoPaterno, numeroSeguroSocial, ventasBrutas y
tarifaComision como private (líneas 6 a 10) y proporciona los métodos public
establecerPrimerNombre, obtenerPrimerNombre, establecerApellidoPaterno,
obtenerApellidoPaterno, establecerNumeroSeguroSocial, obtenerNumeroSeguroSocial,
establecerVentasBrutas, obtenerVentasBrutas, establecerTarifaComision,
obtenerTarifaComision, ingresos y toString para manipular estos valores. Observe que los
métodos ingresos (líneas 85 a 88) y toString (líneas 91 a 98) utilizan los métodos obtener de
la clase para obtener los valores de sus variables de instancia. Si decidimos modificar los
nombres de las variables de instancia, no habrá que modificar las declaraciones de ingresos y
de toString; sólo habrá que modificar los cuerpos de los métodos obtener y establecer que
manipulan directamente estas variables de instancia. Observe que estos cambios ocurren sólo
dentro de la superclase; no se necesitan cambios en la subclase. La localización de los efectos
de los cambios como éste es una buena práctica de ingeniería de software. La subclase
EmpleadoBaseMasComision4 hereda los miembros no private de EmpleadoPorComision3 y
puede acceder a los miembros private de su superclase, a través de esos métodos.
Creación de Subclases (Herencia)
La jerarquía de herencia EmpleadoPorComision-EmpleadoBaseMasComision
mediante el uso de variables de instancia private
Creación de Subclases (Herencia)
La jerarquía de herencia EmpleadoPorComision-EmpleadoBaseMasComision
mediante el uso de variables de instancia private
Creación de Subclases (Herencia)
La jerarquía de herencia EmpleadoPorComision-EmpleadoBaseMasComision
mediante el uso de variables de instancia private
Creación de Subclases (Herencia)
La jerarquía de herencia EmpleadoPorComision-EmpleadoBaseMasComision
mediante el uso de variables de instancia private
Los constructores no se heredan
Cada una de las subclases hereda los métodos y los campos no privados de su
principal (superclase). Sin embargo, la subclase no hereda el constructor de su
principal.
Las declaraciones de constructores no son miembros. Nunca se heredan y, por
tanto, no están sujetas a ocultación o sustitución.
Uso de super
Para crear una instancia de una subclase, normalmente resulta mas fácil llamar al
constructor de la clase principal.
• En su constructor, Manager llama al constructor de Employee.

• La palabra clave super se usa para llamar al constructor de un principal.


• Debe ser la primera sentencia del constructor.
• Si no se proporciona, se inserta una llamada por defecto a super().
• La palabra clave super también se puede usar para llamar al método de un
principal o para acceder a un campo (no privado) de un principal.
Creación de un objeto Manager
La operación de creación de un objeto Manager es similar a la creación de un
objeto Employee:

• Todos los métodos Employee están disponibles para Manager:

• La clase Manager define un nuevo métodos para obtener el valor Department


Name:
Polimorfismo
El polimorfismo nos permite “programar en forma general”, en vez de “programar en forma específi ca”. En
especial, nos permite escribir programas que procesen objetos que compartan la misma superclase en una
jerarquía de clases, como si todos fueran objetos de la superclase; esto puede simplifi car la programación.
Considere el siguiente ejemplo de polimorfismo. Suponga que crearemos un programa que simula el
movimiento de varios tipos de animales para un estudio biológico. Las clases Pez, Rana y Ave representan
los tres tipos de animales bajo investigación. Imagine que cada una de estas clases extiende a la superclase
Animal, la cual contiene un método llamado mover y mantiene la posición actual de un animal, en forma de
coordenadas x-y. Cada subclase implementa el método mover. Nuestro programa mantiene un arreglo de
referencias a objetos de las diversas subclases de Animal. Para simular los movimientos de los animales, el
programa envía a cada objeto el mismo mensaje una vez por segundo; a saber, mover. No obstante, cada
tipo específico de Animal responde a un mensaje mover de manera única; un Pez podría nadar tres pies,
una Rana podría saltar cinco pies y un Ave podría volar diez pies. El programa envía el mismo mensaje (es
decir, mover) a cada objeto animal en forma genérica, pero cada objeto sabe cómo modificar sus
coordenadas x-y en forma apropiada para su tipo específico de movimiento. Confiar en que cada objeto
sepa cómo “hacer lo correcto” (es decir, lo que sea apropiado para ese tipo de objeto) en respuesta a la
llamada al mismo método es el concepto clave del polimorfismo. El mismo mensaje (en este caso, mover)
que se envía a una variedad de objetos tiene “muchas formas” de resultados; de aquí que se utilice el
término polimorfismo.
Polimorfismo
Con el polimorfismo podemos diseñar e implementar sistemas que puedan extenderse con
facilidad; pueden agregarse nuevas clases con sólo modificar un poco (o nada) las porciones
generales de la aplicación, siempre y cuando las nuevas clases sean parte de la jerarquía de
herencia que la aplicación procesa en forma genérica. Las únicas partes de un programa que
deben alterarse para dar cabida a las nuevas clases son las que requieren un conocimiento
directo de las nuevas clases que el programador agregará a la jerarquía. Por ejemplo, si
extendemos la clase Animal para crear la clase Tortuga (que podría responder a un mensaje
mover caminando una pulgada), necesitamos escribir sólo la clase Tortuga y la parte de la
simulación que crea una instancia de un objeto Tortuga. Las porciones de la simulación que
procesan a cada Animal en forma genérica pueden permanecer iguales.
Algunas veces, cuando se lleva a cabo el procesamiento polimórfico, es necesario programar
“en forma específica”. Nuestro ejemplo práctico con Empleado demuestra que un programa
puede determinar el tipo de un objeto en tiempo de ejecución, y actuar sobre ese objeto de
manera acorde. En el ejemplo práctico utilizamos estas capacidades para determinar si
cierto objeto empleado específico es un EmpleadoBaseMasComision. Si es así,
incrementamos el salario base de ese empleado en un 10%.
Ejemplos del polimorfismo
Anteriormente se definió la clase EmpleadoBaseMasComision que heredó de la clase
EmpleadoPorComision. Los ejemplos en esa sección manipularon objetos EmpleadoPorComision y
EmpleadoBaseMasComision mediante el uso de referencias a ellos para invocar a sus métodos; dirigimos
las referencias a la superclase a los objetos de la superclase, y las referencias a la subclase a los objetos de
la subclase. Estas asignaciones son naturales y directas; las referencias a la superclase están diseñadas
para referirse a objetos de la superclase, y las referencias a la subclase están diseñadas para referirse a
objetos de la subclase. No obstante, como veremos pronto, es posible realizar otras asignaciones.
En el siguiente ejemplo, dirigiremos una referencia a la superclase a un objeto de la subclase. Después
mostraremos cómo al invocar un método en un objeto de la subclase a través de una referencia a la
superclase se invoca a la funcionalidad de la subclase; el tipo del objeto actual al que se hace referencia,
no el tipo de referencia, es el que determina cuál método se llamará. Este ejemplo demuestra el concepto
clave de que un objeto de una subclase puede tratarse como un objeto de su superclase. Esto permite
varias manipulaciones interesantes. Un programa puede crear un arreglo de referencias a la superclase,
que se refieran a objetos de muchos tipos de subclases. Esto se permite, ya que cada objeto de una
subclase es un objeto de su superclase. Por ejemplo, podemos asignar la referencia de un objeto
EmpleadoBaseMasComision a una variable de la superclase EmpleadoPorComision, ya que un
EmpleadoBaseMasComision es un EmpleadoPorComision; por lo tanto, podemos tratar a un
EmpleadoBaseMasComision como un EmpleadoPorComision.
Ejemplos del polimorfismo
Como veremos más adelante en este capítulo, no podemos tratar a un objeto de la
superclase como un objeto de cualquiera de sus subclases, porque un objeto
superclase no es un objeto de ninguna de sus subclases. Por ejemplo, no podemos
asignar la referencia de un objeto EmpleadoPorComision a una variable de la subclase
EmpleadoBaseMasComision, ya que un EmpleadoPorComision no es un
EmpleadoBaseMasComision, no tiene una variable de instancia salarioBase y no tiene
los métodos establecerSalarioBase y obtenerSalarioBase. La relación “es un” se aplica
sólo de una subclase a sus superclases directas (e indirectas), pero no viceversa.
El ejemplo demuestra tres formas de usar variables de la superclase y la subclase para
almacenar referencias a objetos de la superclase y de la subclase. Las primeras dos
formas son simples: asignamos una referencia a la superclase a una variable de la
superclase, y asignamos una referencia a la subclase a una variable de la subclase.
Después demostramos la relación entre las subclases y las superclases (es decir, la
relación “es-un” ) mediante la asignación de una referencia a la subclase a una variable
de la superclase.
Ejemplos del polimorfismo
Ejemplos del polimorfismo
En las líneas 10 y 11 crean un objeto EmpleadoPorComision3 y asignan su referencia a una variable
EmpleadoPorComision3. Las líneas 14 a 16 crean un objeto EmpleadoBaseMasComision4 y asignan su referencia
a una variable EmpleadoBaseMasComision4. Estas asignaciones son naturales; por ejemplo, el principal
propósito de una variable EmpleadoPorComision3 es guardar una referencia a un objeto EmpleadoPorComision3.
Las líneas 19 a 21 utilizan la referencia empleadoPorComision para invocar a toString en forma explícita. Como
empleadoPorComision hace referencia a un objeto EmpleadoPorComision3, se hace una llamada a la versión de
toString de la superclase EmpleadoPorComision3. De manera similar, las líneas 24 a 27 utilizan a
empleadoBaseMasComision para invocar a toString de forma explícita en el objeto EmpleadoBaseMasComision4.
Esto invoca a la versión de toString de la subclase EmpleadoBaseMasComision4.
Después, las líneas 30 y 31 asignan la referencia al objeto empleadoBaseMasComision de la subclase a una
variable de la superclase EmpleadoPorComision3, que las líneas 32 a 34 utilizan para invocar al método toString.
Cuando una variable de la superclase contiene una referencia a un objeto de la subclase, y esta referencia se utiliza
para llamar a un método, se hace una llamada a la versión del método de la subclase. Por ende,
empleadoPorComision2.ToString() en la línea 34 en realidad llama al método toString de la clase
EmpleadoBaseMasComision4. El compilador de Java permite este “cruzamiento”, ya que un objeto de una
subclase es un objeto de su superclase (pero no viceversa). Cuando el compilador encuentra una llamada a un
método que se realiza a través de una variable, determina si el método puede llamarse verificando el tipo de clase
de la variable. Si esa clase contiene la declaración del método apropiada (o hereda una), se compila la llamada. En
tiempo de ejecución, el tipo del objeto al cual se refiere la variable es el que determina el método que se utilizará.
Aplicación de polimorfismo
Suponga que se le solicita que cree una nueva clase que calcule las acciones
otorgadas a los empleados según su salario y su rol (supervisor, ingeniero o
administrador):
Aplicación del polimorfismo
Una buena práctica consiste en transferir parámetros y escribir métodos que usen el
formato más genérico del objeto posible.
Uso de la palabra clave instaceof
El lenguaje Java proporciona la palabra clave instanceof para determinar un tipo de
clase de objeto en tiempo de ejecución.
Conversión de referencias de objetos
Después de usar el operador instanceof para verificar que el objeto recibido como
argumento es una subclase, puede acceder a toda la funcionalidad del objeto
convirtiendo la referencia:

Sin la conversión a Manager, el método setDeptName no se compilaría.


Polimorfismo - Reglas de conversión
Las conversiones ascendentes siempre están permitidas y en ellas no se necesita un
operador cast.
Polimorfismo - Reglas de conversión
En el caso de las conversiones descendentes, el compilador debe aceptar que la
conversión es, al menos posible.
Acceso a miembros de una
clase
Uso del control de acceso
Se han visto las palabras clave public y private. Hay cuatro niveles de acceso que se
pueden aplicar a los métodos y los campos de datos.

Modificador Misma clase Mismo Subclase de Universo


(palabra paquete otro paquete
clave)
Private Si
Por defecto Si Si
Protected Si Si Si
public Si Si Si Si
Control de acceso protegido
Control de acceso: Recomendación
Una buena práctica al trabajar con campos es hacer que sean tan poco accesibles
como sea posible y especificar claramente el uso de los campos en los métodos.
Declaración de métodos
Sobrecarga de métodos
Su diseño puede llamar varios métodos de la misma clase con el mismo nombre,
pero con distintos argumentos.

• Java le permite reutilizar un nombre de método para más de un método.


• Se aplican dos reglas a los métodos sobrecargados:
• Las listas de argumentos deben ser distintas: en orden, numero o tipo.
• Los tipos de retorno pueden variar.

• Por tanto, el siguiente ejemplo no es valido:


Métodos con argumentos variables
Una variación a la sobrecarga de métodos es cuando se necesita un método que
tome cualquier número de argumentos del mismo tipo:

• Estos tres métodos sobrecargados comparten la misma funcionalidad. Estaría


bien reducir estos métodos a uno solo.
Métodos con argumentos variables
• Java proporciona una función denominada varargs o argumentos variables.

• Tenga en cuanta que el argumentos nums es realmente un objeto de matriz de


tipo int[]. Esto permite al método iterarse y repetir cualquier cantidad de
elementos.
Sustitución de métodos
Considere un requisito para proporcionar una cadena que represente algunos
detalles sobre los campos de la clase Employee.
Sustitución de métodos
En la clase Manager, mediante la creación de un método con la misma firma que el
método de la clase Employee, está sustituyendo el método getDetails:
Llamada a un método sustituido
• Con los ejemplos anteriores de Employee y Manager:

• Se llama al método getDetails correcto de cada clase:


Accesibilidad de los métodos sustituidos
Un método sustituido no puede ser menos accesible que el método de la clase
principal.
Sustitución de métodos de objeto
Una de la ventajas de la herencia única es que cada una de las clases tiene un objeto
principal por defecto. La clase raíz de cada clase Java es java.lang.Object.
• No es necesario que se declare que la clase amplía de Object. El compilador se
encarga de esa tarea.

• Es equivalente a:

• La clase raíz contiene varios métodos que no son finales, pero hay tres que son
importantes para pensar en la sustitución:
• toString, equals y hashCode
Método Object toString
Al método toString se le llama siempre que se transfiera una instancia de la clase a
un método que tome un objeto String, como println:

• Puede utilizar toString para proporcionar información de la instancia:

• Este enfoque para obtener detalles sobre la clase es mejor que crear su propio
método getDetails.
Método Object equals
El método Object equals solo compara referencia de objetos.
• Si hay dos objetos x e y en cualquier clase, x es igual a y si y solo si x e y hacen
referencia al mismo objeto.

• Ya que lo que realmente se desea es probar el contenido del objeto Employee, es


necesario sustituir el método equals:
Sustitución de equals en Employee
Un ejemplo de sustitución del método equals en la clase Employee compara todos
los campos para ver si tienen igualdad:
Sustitución de Object hashCode
El contrato general de Object indica que si dos objetos se consideran iguales (con el
método equals), el código hash devuelto para los dos objetos también debe ser
igual.
Palabra clave static
El modificador static se usa para declarar campos y métodos como recursos de nivel
de clase. Los miembros de clases estaticas:
• Se pueden usar sin instancias de objetos.
• Se usan cuando un problema se soluciona mejor sin objetos.
• Se usan cuando hay objetos del mismo tipo que deben compartir campos.
• Se se deben usar para evitar el uso de funciones orientadas a objetos de Java a
menos que haya un motivo justificado.
Métodos estáticos
Los métodos estáticos son métodos que s pueden llamar incluso si la clase en la que
se hayan declarado no se ha instanciado. Los métodos estáticos:
• Se denominan métodos de clase.
• Son útiles para las API que no están orientadas a objetos.
• java.lang.Math contiene muchos métodos estáticos.

• Se suelen usar en lugar de los constructores para realizar tareas relacionadas con
la inicialización de objetos.
• No pueden acceder a miembros no estáticos de la misma clase.
• Se pueden ocultar en subclases, pero no se pueden sustituir.
• Sin llamada al método virtual.
Implantación de métodos estáticos
Llamada a métodos estáticos

Al llamar a los métodos estáticos, debería:


• Cualificar la ubicación del método con un nombre de clase si el método se encuentra en
otra clase distinta a la del emisor de la llamada.
• No es necesario para métodos de la misma clase.

• Evitar el uso de una referencia de objetos para llamar a un método estático.


Variables estáticas
Las variables estáticas son variables a las que se puede acceder incluso aunque la
clase en la que se hayan declarado no se haya instanciado. Las variables estáticas:
• Se denominan variables de clase.
• Se limitan a una sola copia por JVM.
• Son útiles para contener datos compartidos.
• Los métodos estáticos almacenan datos en variables estáticas.
• Todas las instancias de objetos comparten una sola copia de cualquier variable estática.

• Se inicializan cuando la clase contenedora se carga por primera vez.


Definición de variables estáticas
Uso de variables estáticas

Al acceder a las variables estáticas, debería:


• Cualificar la ubicación de la variable con un nombre de clase si la variable se
encuentra en otra clase distinta a la del emisor de la llamada.
• No es necesario para variables de la misma clase.

• Evitar el uso de una referencia de objeto para acceder a una variable estática.
Métodos finales
Un método se puede declarar como final. Los métodos finales no se pueden
sobrescribir.
Clases finales
Una clase se puede declarar como final. Las clases finales no se pueden ampliar.
Variables finales
El modificador final se puede aplicar a las variables. Las variables finales no pueden
cambiar sus valores una vez inicializadas. Las variables finales pueden ser:
• Campos de clase
• Los campos finales con expresiones de constantes de tiempo de compilación son
variables de constantes.
• Los campos estáticos se pueden combinar con los finales para crear una variable
siempre disponible y que nunca cambia.

• Parámetros del método.


• Variables locales

Nota: las referencias finales siempre deben hacer referencia al mismo objeto, pero
el contenido de ese objeto se puede modificar.
Variables finales
Cuándo evitar las constantes
Las variables public, static y final pueden ser muy útiles, pero hay un patrón de uso
concreto que se debería evitar. Las constantes pueden proporcionar una falsa
sensación de validación de los datos introducidos o de comprobación del rango de
valores.
• Piense en un método que solo deba recibir uno de los tres valores posibles:

• Las siguientes líneas de código se seguirían compilando:


Reutilización de código
La duplicación del código (copiar y pegar) puede conllevar problemas de
mantenimiento. No desea corregir el mismo bug una y otra vez.
• “No se repita.”
• Reutilice el código de la forma correcta:
• Refactorice rutinas de uso común en bibliotecas.
• Mueva el comportamiento que comparten las clases hermanas a su clase principal.
• Cree nuevas combinaciones de comportamientos combinando varios tipos de objetos.
Constructores y
Destructores
Constructor
Un constructor es un método que tiene el mismo nombre que la clase y cuyo
propósito es inicializar los miembros datos de un nuevo objeto que se ejecuta
automáticamente cuando se crea un objeto de una clase. Sintácticamente es similar
a un método. Dependiendo del número y tipos de los argumentos proporcionados,
una función o método constructor se llama automáticamente cada vez que se crea
un objeto. Si no se ha escrito ninguna función constructor en la clase, el compilador
proporciona un constructor por defecto. A su rol como inicializador, un constructor
puede también añadir otras tareas cuando es llamado.
Un constructor tiene el mismo nombre que la propia clase. Cuando se define un
constructor no se puede especificar un valor de retorno, ni incluso nada (void); un
constructor nunca devuelve un valor. Un constructor puede, sin embargo, tomar
cualquier número de parámetros (cero o más).
Destructor
La contrapartida a un constructor es un destructor. Los destructores son funciones
(métodos) que tienen el mismo nombre de la clase al igual que los constructores,
pero para distinguirlos sintácticamente se les precede por una tilde (~) o por la
palabra reservada destructor.
Los destructores se llaman automáticamente siempre que un objeto deje de existir y
su objetivo es limpiar cualquier efecto no deseado que haya podido dejar el objeto.
Garbage Collector
Recolector de basura
Como los objetos se asignan dinámicamente, cuando estos objetos se destruyen será
necesario verificar que la memoria ocupada por ellos ha quedado liberada para usos
posteriores. El procedimiento de liberación es distinto según el tipo de lenguaje
utilizado.
En C++ los objetos asignados dinámicamente se deben liberar utilizando un
operador delete. Por el contrario, Java y C# tienen un enfoque diferente. Manejan
la liberación de memoria de modo automático. La técnica que utilizan se denomina
recolección de basura (garbage collection). Su funcionamiento es el siguiente:
cuando no existe ninguna referencia a un objeto, se supone que ese objeto ya no se
necesita, y la memoria ocupada por ese objeto puede ser recuperada (liberada). No
hay necesidad de destruir objetos explícitamente como hace C++. La recolección de
basura sólo ocurre esporádicamente durante la ejecución de su programa. No
sucede simplemente porque los objetos dejen de ser utilizados.
Relaciones entre clases y
dependencias
Modelo de clases
Un diagrama de clases sirve para visualizar las relaciones entre las clases que
involucran el sistema, las cuales pueden ser asociativas, de herencia, de uso y de
contenimiento.
Un diagrama de clases esta compuesto por los siguientes elementos:
• Clase: atributos, métodos y visibilidad.
• Relaciones: Herencia, Composición, Agregación, Asociación y Uso.
Clase
Es la unidad básica que encapsula toda la información de un Objeto (un objeto es
una instancia de una clase). A través de ella podemos modelar el entorno en estudio
(una Casa, un Auto, una Cuenta Corriente, etc.).
En UML, una clase es representada por un rectángulo que posee tres divisiones:
Relaciones entre Clases
Ahora ya definido el concepto de Clase, es necesario explicar como se pueden
interrelacionar dos o más clases (cada uno con características y objetivos
diferentes).
Antes es necesario explicar el concepto de cardinalidad de relaciones: En UML, la
cardinalidad de las relaciones indica el grado y nivel de dependencia, se anotan en
cada extremo de la relación y éstas pueden ser:
• uno o muchos: 1..* (1..n)
• 0 o muchos: 0..* (0..n)
• número fijo: m (m denota el número).
Herencia (Especialización/Generalización):
Indica que una subclase hereda los métodos y atributos especificados por una Super
Clase, por ende la Subclase además de poseer sus propios métodos y atributos,
poseerá las características y atributos visibles de la Super Clase (public y protected),
ejemplo:
Agregación
Para modelar objetos complejos, n bastan los tipos de datos básicos que proveen los
lenguajes: enteros, reales y secuencias de caracteres. Cuando se requiere componer
objetos que son instancias de clases definidas por el desarrollador de la aplicación,
tenemos dos posibilidades:
• Por Valor: Es un tipo de relación estática, en donde el tiempo de vida del objeto
incluido esta condicionado por el tiempo de vida del que lo incluye. Este tipo de
relación es comúnmente llamada Composición (el Objeto base se construye a
partir del objeto incluido, es decir, es "parte/todo").
• Por Referencia: Es un tipo de relación dinámica, en donde el tiempo de vida del
objeto incluido es independiente del que lo incluye. Este tipo de relación es
comúnmente llamada Agregación (el objeto base utiliza al incluido para su
funcionamiento).
Agregación

• Un Almacén posee Clientes y Cuentas (los rombos van en el objeto que posee las
referencias).
• Cuando se destruye el Objeto Almacén también son destruidos los objetos Cuenta
asociados, en cambio no son afectados los objetos Cliente asociados.
• La composición (por Valor) se destaca por un rombo relleno.
• La agregación (por Referencia) se destaca por un rombo transparente.
Asociación
La relación entre clases conocida como Asociación, permite asociar objetos que
colaboran entre si. Cabe destacar que no es una relación fuerte, es decir, el tiempo
de vida de un objeto no depende del otro.

Un cliente puede tener asociadas muchas Ordenes de Compra, en cambio una orden
de compra solo puede tener asociado un cliente.
Dependencia o Instanciación
Representa un tipo de relación muy particular, en la que una clase es instanciada
(su instanciación es dependiente de otro objeto/clase). Se denota por una flecha
punteada.
El uso más particular de este tipo de relación es para denotar la dependencia que
tiene una clase de otra, como por ejemplo una aplicación grafica que instancia una
ventana (la creación del Objeto Ventana esta condicionado a la instanciación
proveniente desde el objeto Aplicación):
Ejemplo
Se quiere desarrollar un sistema de información para la Universidad de Oriente según la
descripción siguiente.
• La Universidad se caracteriza mediante su nombre y la ciudad donde se sitúa.
• En la Universidad están vinculados dos tipos de Persona: Trabajadores, que
la Universidad emplea, y Estudiantes, que estudian en la Universidad.
• Cada Persona tiene una CI (Código de identificación) y un nombre.
• Los Trabajadores pertenecen a dos grupos: PDI (Personal docente) y PAS (Personal
administrativo).
• Cada Trabajador tiene asociada una fecha de inicio de su contrato.
• Cada miembro del PDI también tiene una categoría, mientras que cada miembro
del PAS tiene un puesto.
• Los miembros del PDI pueden o no ser Doctores. Las actividades que desarrolla el PDI son
investigar y enseñar, mientras que la actividad que desarrolla el PAS es administrar.
Ejemplo
• La Universidad se compone de un conjunto de Departamentos, cada uno de los cuales tiene
un nombre y un conjunto de Trabajadores adscrito.
• Un Trabajador no puede estar adscrito a más de un Departamento.
• Un PDI está adscrito obligatoriamente a un Departamento, mientras que un PAS, no.
• Cada Departamento está dirigido por un Doctor.
• Un Estudiante puede ser bien Estudiante de grado, de una determinada titulación, o bien
Estudiante de Doctorado, de un determinado programa de Doctorado.
• Un Estudiante de grado puede también colaborar con un Departamento como becario.
• Un Estudiante de Doctorado realiza una tesis dirigida por un Doctor.
• Puede suponer que un Estudiante no puede estudiar en más de una Universidad y que
un Trabajador no puede ser empleado por más de una Universidad.
• Una Persona puede ser a la vez Trabajador y Estudiante, Un Estudiante no puede ser a la
vez Estudiante de grado y Estudiante de Doctorado, los únicos tipos de Trabajador que
existen son PDI y PAS, un Trabajador no puede ser a la vez PDI y PAS.
Ejemplo
Ejercicio

También podría gustarte