Tesis Héctor César Lazzarini UNIVERSIDAD NACIONAL DE MISIONES FACULTAD DE CIENCIAS ECONOMICAS MAESTRIA EN INFORMÁTICA Y COMPUTACIÓN CONVENIO CON LA UNIVERSIDAD DE CANTABRIA TESIS PROGRAMACION ORIENTADA A OBJETOS D Diirre ecctto orra a:: M Ma ag giisstte err G Glla ad dyyss N No oe em míí D Da ap po ozzo o M a e s t r a n d o : H é c t o r C é s a r L a z z a r i n i Maestrando: Héctor César Lazzarini Dirección Postal: Moreno 155 – P3600KAC Formosa – Formosa – Argentina Dirección electrónica: lazzariniylazzarini@arnet.com.ar 1 Tesis Héctor César Lazzarini TABLA DE CONTENIDOS TABLA DE CONTENIDOS........................................................................................................ 1 Agradecimientos:........................................................................................................................... 7 Prólogo .......................................................................................................................................... 9 Capitulo 1 .................................................................................................................................... 11 INTRODUCCIÓN A LA PROGRAMACIÓN ORIENTADA A OBJETOS (POO) ..................... 11 Un poco de historia de la POO........................................................................................... 11 Ideas básicas de la POO .................................................................................................... 14 Ventajas e inconvenientes de la orientación a objetos ...................................................... 15 El modelo OO ..................................................................................................................... 15 Definiciones básicas ........................................................................................................... 16 Diferencia entre el modelado por descomposición funcional y el OO................................ 17 Descomposición OO........................................................................................................... 18 Capitulo 2. ................................................................................................................................... 19 PROGRAMACIÓN ORIENTADA A OBJETOS – FIJANDO CONCEPTOS ........................... 19 Introducción. ....................................................................................................................... 19 Información ......................................................................................................................... 19 Sistemas de información. ................................................................................................... 20 Profundizando el concepto de Objeto. ............................................................................... 20 Demonios............................................................................................................................ 23 Abstracción. ........................................................................................................................ 24 Modularidad. ....................................................................................................................... 24 Encapsulación de objetos................................................................................................... 24 Reutilización. ...................................................................................................................... 25 Clase................................................................................................................................... 25 Herencia.............................................................................................................................. 26 Polimorfismo. ...................................................................................................................... 27 Jerarquías. .......................................................................................................................... 27 Mensajes............................................................................................................................. 28 Capitulo 3. ................................................................................................................................... 29 JAVA Y LA PROGRAMACIÓN ORIENTADA A OBJETOS.................................................... 29 Introducción. ¿Porqué Java?.............................................................................................. 29 Origen de Java. .................................................................................................................. 29 Características de Java ...................................................................................................... 31 Capitulo 4. ................................................................................................................................... 37 PROFUNDIZANDO CONCEPTOS DE POO – ejemplos con JAVA ...................................... 37 1. Herencia.......................................................................................................................... 38 2. Sobrescritura de métodos............................................................................................... 38 3. La clase base y la clase derivada................................................................................... 38 En Java disponemos de los paquetes básicos: ............................................................. 39 La clase base object. ...................................................................................................... 45 Métodos que pueden ser redefinidos por el programador. ............................................ 45 Concretando: .................................................................................................................. 48 4. Redefinición de métodos heredados .............................................................................. 48 Modificador final:............................................................................................................. 49 5. Clases y métodos abstractos ......................................................................................... 49 6. Polimorfismo ................................................................................................................... 49 Métodos (funciones miembro). ....................................................................................... 52 Métodos sobrecargados (overloaded)............................................................................ 54 7. Vinculación, Ligadura o Binding ..................................................................................... 49 8. Redefinición .................................................................................................................... 55 Sobrecarga de Constructores......................................................................................... 55 Objetos como parámetros .............................................................................................. 56 Paso de argumentos. ..................................................................................................... 56 El operador static............................................................................................................ 57 Las variables static ......................................................................................................... 57 Los métodos static.......................................................................................................... 58 3 Tesis Héctor César Lazzarini El operador final.............................................................................................................. 59 9. Interfases. ....................................................................................................................... 59 ¿Qué diferencia hay entre una interfase y una clase abstract?..................................... 59 Definición de interfases. ................................................................................................. 60 Utilización de interfases.................................................................................................. 60 Conceptos Útiles............................................................................................................. 61 La llamada a super. ........................................................................................................ 61 10. Permisos de acceso en java......................................................................................... 61 Accesibilidad de los Packages (paquetes)..................................................................... 61 Accesibilidad de las Variables y Métodos Miembros de una Clase. .............................. 62 Resumen de los permisos de acceso de Java............................................................... 62 11. Transformaciones de tipo: casting................................................................................ 63 Conversión de tipos primitivos........................................................................................ 63 12. Sincronización de threads (hilos). ................................................................................ 63 Capítulo 5. ................................................................................................................................... 65 Implementación de los conceptos de oo con JAVA................................................................ 65 Implementado el concepto Jerarquía ................................................................................. 65 La clase Figura ............................................................................................................... 66 La clase Rectángulo ....................................................................................................... 66 La clase Círculo .............................................................................................................. 67 Uso de la jerarquía de clases. ............................................................................................ 67 Enlace dinámico o binding.................................................................................................. 68 Ejemplo de polimorfismo. ............................................................................................... 68 Ampliación de la jerarquía de clases.................................................................................. 70 La clase Triangulo. ......................................................................................................... 71 El operador instanceof........................................................................................................ 72 En resumen:........................................................................................................................ 72 Clases y Métodos Finales................................................................................................... 73 Interfases. ........................................................................................................................... 74 Diferencias entre una Interfase y una Clase Abstracta. ..................................................... 75 Las interfases y el Polimorfismo......................................................................................... 76 Herencia simple. ................................................................................................................. 76 Composición. ...................................................................................................................... 79 La clase punto ................................................................................................................ 80 La clase Rectangulo ....................................................................................................... 81 Definiendo Clases............................................................................................................... 83 Uso de Atributos. ................................................................................................................ 85 Uso de la Definición de Clase............................................................................................. 86 Diseño e Implementación de Clases. ................................................................................. 86 Diseño de la clase: ............................................................................................................. 87 Implementación de la clase: ............................................................................................... 88 Primer Ejemplo Completo: Clase para Entrada/Salida Interactiva (InteractiveIO)......... 88 Segundo Ejemplo Completo: Clase Nombre.................................................................. 92 Programa Hello world ! Revisado ................................................................................. 100 Capítulo 6. ................................................................................................................................. 101 Calidad del Software......................................................................................................... 102 Objetivo de la Calidad en los Sistemas ............................................................................ 103 Control de la Calidad ........................................................................................................ 103 Control de Calidad del Software ....................................................................................... 104 Administración de la Calidad ............................................................................................ 108 Calidad por Etapas ........................................................................................................... 110 Estándares y Modelos de Calidad en la Ingeniería de Software...................................... 111 La Norma ISO 9000.......................................................................................................... 112 El Modelo Tick IT .............................................................................................................. 114 El Modelo CMM ................................................................................................................ 115 El Modelo BOOTSTRAP................................................................................................... 118 El Modelo ISO/SPICE....................................................................................................... 120 Conclusiones......................................................................................................................... 123 4 Tesis Héctor César Lazzarini La elección del Lenguaje de Programación ..................................................................... 123 Perspectivas a futuro ............................................................................................... 125 Bibliografía ............................................................................................................................ 131 PROGRAMAS EJEMPLO DE LOS TEMAS TRATADOS. ................................................... 133 Redefinición de métodos .................................................................................................. 133 /*Programa 1 - Versión 1*/ ........................................................................................ 133 /*Programa 1 - Versión 2*/ ........................................................................................ 134 /*Programa 1 - Versión 3*/ ........................................................................................ 135 /* Programa 2 */ .......................................................................................................... 137 Sobrecarga de métodos ................................................................................................... 138 /*Programa 1 - Versión 1*/ ........................................................................................ 138 /*Programa 1 - Versión 2*/ ........................................................................................ 139 /** Programa 2 */........................................................................................................... 141 /*Programa 3 */ ............................................................................................................. 143 Explicación del Algoritmo.................................................................................................. 144 /*Programa 4 */ ............................................................................................................. 145 Clases abstractas ............................................................................................................. 147 /** Programa 1 */........................................................................................................... 147 /**Programa 2 – versión 1*/.......................................................................................... 148 /**Programa 2 – versión 2*/.......................................................................................... 149 Polimorfismo ..................................................................................................................... 151 /*Programa 1 – versión1 */ ........................................................................................... 151 /*Programa 2 – versión 1 */ .......................................................................................... 153 /**Programa 2 – versión 2 */......................................................................................... 156 /**Programa 3 */ /**subprograma 1*/............................................................................ 160 /** subprograma 2*/ ...................................................................................................... 160 /** subprograma 3*/ ...................................................................................................... 160 Ligaduras dinámicas......................................................................................................... 163 /** Programa 1 */ /** Subprograma 1*/ ......................................................................... 163 /**Subprograma 2*/ ...................................................................................................... 164 5 Tesis Héctor César Lazzarini 6 Tesis Héctor César Lazzarini Agradecimientos: A toda la gente me alentó para que termine este trabajo. A todos los profesores de España, realmente es imposible dejar de recordarlos, por su calidez humana y profesional. A todos: un “GRACIAS” sincero. Formosa, 14 de mayo de 2005 7 Introducción Prólogo Los objetivos de este trabajo son establecer las pautas básicas para introducir a los alumnos en el mundo de la Programación Orientada a Objetos (POO), en el marco de la materia PROGRAMACIÓN IV de la carrera Licenciatura en Sistemas de Información. En este libro se enseñaran los conceptos de la Programación Orientada a Objetos con abundantes ejemplos basados en el lenguaje JAVA. Muchos de estos ejemplos van incorporando progresivamente cada nuevo concepto que se enseña, para, de esta manera, facilitar la incorporación de un nuevo concepto con un ejemplo ya visto. He tratado, en este trabajo, de compatibilizar ambas cosas. Puntualmente, y tal lo afirmado en el Proyecto original de esta Tesis, necesito formar a los alumnos con un concepto “entendible” de la POO, porque no debemos perder de vista que es una materia con la cual estos se reciben de Programadores Universitarios, por lo que deben aplicar inmediatamente estos conceptos. Debo indicar que he escogido el lenguaje JAVA como soporte para explicar los conceptos a enseñar por su popularidad comercial y su aplicación actual en Internet. Este trabajo de Tesis se ha estructurado de la siguiente manera: Introducción histórica. Explicación conceptual de los temas planteados. No se incluyen ejemplos de ningún tipo. Explicación de los conceptos con ejemplos básicos. Se revisan los conceptos brevemente y se aplica a un ejemplo preciso. Presentación de programas en Java que aplican puntualmente los conceptos impartidos. Se trata de programas que funcionan autónomamente. Presentación de diversos programas más genéricos que combinan varios conceptos combinados. Conclusiones y perspectivas a futuro. Bibliografía utilizada. Glosario Listado de códigos de programas desarrollados donde se analiza cada concepto en particular. 9 Capítulo 1 – Introducción a la Programación Orientada a Objetos. Capitulo 1 INTRODUCCIÓN A LA PROGRAMACIÓN ORIENTADA A OBJETOS (POO) Un poco de historia de la POO. 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. 11 Capítulo 1 – Introducción a la Programación Orientada a Objetos. A principios de los 90 se popularizó un nuevo lenguaje orientado a objetos. Se trata del C++ creado por Bjarne Stroustrup. La idea de Bjarne Stroustrup fue crear un lenguaje orientado a objetos que heredara prácticamente toda la sintaxis y posibilidades del lenguaje que en ese momento era más popular entre los programadores, el lenguaje C. Este truco ayudó a popularizar la programación orientada a objetos y preparó el camino para la aparición del Java. Los creadores de Java aprendieron bien la lección de Bjarne Stroustrup. Un nuevo lenguaje para tener éxito debía ser muy parecido al que en el momento de su lanzamiento fuese el más popular. Así, Java se creó con un gran parecido al C++. Pero Java es un lenguaje más puro de programación orientada a objetos, conserva un poco del C original, pero se parece más al C++. Puede verse al C++ como un paso intermedio en la transición de la programación estructurada del C a la programación orientada a objetos más pura del Java. Microsoft está tratando de impulsar un nuevo lenguaje llamado C# (C sharp) que promete ser aún más puro como lenguaje orientado a objetos, adopta prácticamente todas las mejoras de Java y agrega algunas nuevas. Habrá que estar pendientes una vez lanzado a los programadores de sus posibles ventajas y probables desventajas (en computación nunca se gana algo sin perder otra cosa, lo importante es que en promedio las cosas vayan mejorando). Hacia los 80’s el paradigma orientado a objetos comenzaba a madurar como un enfoque concreto de desarrollo de software. En los últimos años esta metodología ha experimentado un gran progreso, tanto en el desarrollo de programas como en la forma de presentar las aplicaciones del sistema al usuario. 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 tipo de 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. 12 Capítulo 1 – Introducción a la Programación Orientada a Objetos. 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. Wirth, creador del Pascal, nunca quedó satisfecho con este lenguaje. En su constante análisis de los hábitos de programación encontraba que todavía había muchas cosas que mejorar. Una década después del Pascal publicó otro lenguaje llamado Modula2 en el que presentaban algunas de sus ideas más recientes. Los módulos de Modula2 pretendían representar objetos o clases. Dentro de un módulo se definían variables que representaban el estado del objeto y se definían procedimientos que describían su comportamiento. Las variables y los procedimientos de un módulo podían ser de uso público o privado, exclusivos de la clase, según lo decidiera el creador del módulo. Con todo esto se mejoraba notoriamente lo instituido por el Pascal donde el concepto de procedimientos o funciones privadas no existía y se daba un paso importante hacia la programación de clases o programación orientada a objetos. Alrededor de los años 70’s fue desarrollado el lenguaje de programación OO llamado SMALLTALK en los laboratorios Xerox en Palo Alto, EE.UU. Éste lenguaje adoptó los conceptos nombrados anteriormente como su fundamento. El hecho de ser creado en EE.UU., ayudó a que se introdujera a escala mundial el término de Orientación a Objetos (Object Oriented OO) y que cobrara importancia entre los diseñadores de lenguajes de programación. Los puntos importantes de este lenguaje fueron, por un lado, adoptar el concepto de objeto y clase como núcleo del lenguaje y la programación interactiva, incorporando las ideas ya conocidas de lenguajes funcionales. Es decir que se tuviese un lenguaje interpretado y no compilado. En 1985, Bjarne Stroustrup extendió el lenguaje de programación C a C++, es decir C con conceptos de clases y objetos, cerca de esta fecha, 1986, se creó desde sus bases el lenguaje EIFFEL por B. Meyer. Ambos manejan conceptos de objetos y herencia de clases. La herencia es múltiple, y se introduce pensando en dar mayor 13 Capítulo 1 – Introducción a la Programación Orientada a Objetos. flexibilidad a los desarrolladores. Sin embargo, actualmente la herencia múltiple se ha evitado por agregar complejidad en las estructuras de clases. Ambos lenguajes tuvieron importancia entre 1985 y hasta la primera mitad de los 90’s. En 1995 apareció JAVA, el más reciente lenguaje OO, desarrollado por la empresa SUN Microsystems, que hereda conceptos de C++, pero los simplifica y evita la herencia múltiple. En su lugar se introduce el término de interfaz, y la herencia múltiple de interfases. Obtiene una rápida aceptación gracias a los applets, que son programas en JAVA insertados en páginas WEB dentro del código HTML. Estos programas pueden viajar a través de la Internet y brindarle al usuario mayor interactividad con las páginas WEB. JAVA introduce también, la programación concurrente y distribuida. El lenguaje es mitad compilado y mitad interpretado dando como resultado la portabilidad a distintas plataformas. JAVA aun sigue evolucionando y se espera que en los próximos años logre la madurez adecuada para convertirse en un lenguaje de desarrollo de mayor importancia. Ideas básicas de la POO La programación orientada a objetos es una metodología que descansa en el concepto de objeto para imponer la estructura modular de los programas. Permite comprender el dominio del problema a resolver, al intentar construir un modelo del mundo real que envuelve nuestro sistema. Es decir, la representación de este mundo mediante la identificación de los objetos que constituyen el vocabulario del dominio del problema, su organización y la representación de sus responsabilidades. El paradigma orientado a objetos se basa en los tres métodos de organización que utilizamos desde nuestra infancia y en los que basamos todo nuestro pensamiento: a) la diferencia entre un objeto y sus atributos (por ejemplo, entre una manzana y su sabor o peso), b) la diferencia entre un objeto y sus componentes (por ejemplo, entre una manzana y su piel o semillas) y c) la formación y distinción entre clases de objetos (por ejemplo, entre manzanas rojas, manzanas verdes, etc.) La idea de manejar objetos reales como contenedores de estados y comportamientos, es mucho más atractiva desde el punto de vista del usuario. De esta manera no tendrá que batallar con construcciones orientadas al computador, sino que podrá manejar objetos (y operaciones) que se asemejen más a sus equivalentes en el mundo real. La elevación del nivel de abstracción es sin duda un objetivo deseable. Las técnicas orientadas a objetos usan un mismo modelo conceptual para el análisis, el diseño y la programación. La transición desde el análisis al diseño es tan natural que es difícil especificar donde comienza uno y donde acaba el otro. 14 Capítulo 1 – Introducción a la Programación Orientada a Objetos. 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. El modelo OO Podemos indicar que se apoya en cuatro conceptos básicos: objeto clase herencia envío de mensajes Los primeros tres conceptos se refieren a la parte estructural o estática del modelo y el cuarto, que corresponde a mensajes, se refiere a la parte del comportamiento dinámico. 15 Capítulo 1 – Introducción a la Programación Orientada a Objetos. Definiciones básicas Definición de Objeto: En términos más generales, un objeto es una abstracción conceptual del mundo real que se puede traducir a un lenguaje computacional o de programación OO. La abstracción de objeto se caracteriza por tener una identidad única que lo distingue de otros objetos. También tiene un estado, que permite informar lo que éste representa y su comportamiento, es decir lo que él sabe hacer. Hablando en términos computacionales, la identidad del objeto se puede interpretar como la referencia. El estado de objeto es una lista de variables conocidas como sus atributos, cuyos valores representan el estado que caracteriza al objeto. El comportamiento es una lista de métodos, procedimientos, funciones u operaciones que un objeto puede ejecutar a solicitud de otros objetos. Los objetos también se conocen como instancias. Definición de Clase: Es una colección de objetos similares. Estos objetos deben tener los mismos atributos con valores posiblemente diferentes asignados a estos, y el mismo conjunto de métodos que definen su comportamiento. Por otro lado también se puede ver una clase como un molde, esquema o un patrón que define la forma de sus objetos. O bien, como la estructura estática que define: a) el esquema de estados, y b) el comportamiento que van a tener los objetos A partir de ese esquema, dinámicamente durante la ejecución de un programa, se van a ir creando objetos que pertenezcan a esa clase. Definición de Herencia: Una clase puede heredar sus atributos y métodos a varias subclases (la clase que hereda es llamada superclase). Esto significa que una subclase, aparte de los atributos y métodos propios, tiene incorporados los atributos y métodos heredados de la superclase. Una subclase puede a su vez comportarse como una superclase y heredar a otras clases, creando de esta manera la jerarquía de herencia. Cuando una clase hereda de más de una superclase se conoce como herencia múltiple. Definición de Envío de Mensajes: ¿Qué sucede cuando los objetos desean comunicarse entre sí?. Un objeto recibe un estímulo externo de solicitud de un servicio que se traduce en la invocación de un método de éste objeto. Al ejecutarse el método, el objeto puede solicitar servicios de otros objetos, enviándoles mensajes que implican a su vez la invocación de sus métodos, y dentro de estos, nuevamente invocar servicios de otros objetos y así sucesivamente. Este envío de mensajes en cascada representa el comportamiento dinámico del modelo de objetos. 16 Capítulo 1 – Introducción a la Programación Orientada a Objetos. Haciendo un paréntesis, es importante diferenciar entre lenguajes OO y lenguajes que no lo son. Tenemos lenguajes: Base-objetos: Lenguajes que no tienen herencia. Presentan un concepto parecido a clase y alguna forma de crear objetos a partir de ésta. Orientado a objetos: Presentan el concepto de objetos, clases y herencia de clases. Diferencia entre el modelado por descomposición funcional y el OO. Veamos la diferencia con un ejemplo Problema: Hacer bife criollo Resolución según la Descomposición funcional: Se declara una lista de datos, los cuales se encuentran en un espacio común. El algoritmo divide al problema en sub-problemas o funciones, partiendo de las más complejas hasta llegar a las acciones más sencillas de realizar, llamadas también atómicas. En todo momento la descomposición del problema se hará identificando las acciones (verbos) que se deben realizar. Hacer bife criollo acompañado de papa se divide en dos subproblemas: freír bifes con cebollas añadir acompañamiento Freír bifes con cebollas presenta tres subdivisiones: freír bifes preparar salsa con cebolla, perejil y pimientos verdes juntar bifes con la salsa Freír bifes y juntar bifes con la salsa son actividades que ya se saben realizar (atómicas). Sin embargo, el caso de preparar salsa con pimientos verdes aún podemos aclararlo más: Rehogar cebolla, el pimiento verde y el perejil Agregar 1 ajo picado chico freír la salsa que ya son actividades atómicas. Para el caso de añadir acompañamiento se particiona en dos actividades atómicas añadir papa cocida 17 Capítulo 1 – Introducción a la Programación Orientada a Objetos. añadir perejil Si se deseara ahora preparar bife criollo con salsa de pimiento rojo, se tendría que buscar cuales actividades hay que modificar. En este caso sería la parte de los datos y preparar salsa verde, rehogar cebolla pimiento verde y perejil, en el caso de nuestro ejemplo. Sin embargo, lo complicado no termina en modificarlos, ya que no se sabe si en alguna otra parte del sistema se puedan referenciar y entonces esto traiga problemas. Descomposición OO La modelación de esta solución se realiza a través de los sustantivos que definen al problema. En este caso los sustantivos (nombres) más importantes son: Bife_Criollo, Salsa_Verde, Ingredientes, Licuadora, Sartén y Acompañamiento. Todos estos van a ser nuestros objetos que internamente van a integrar datos y operaciones, que podemos asociarlos de manera lógica a cada uno de sus conceptos. Cuando se pide haz_bife_criollo al objeto Bife_Criollo, este debe tener un método que sabe hacer bife criollo apoyándose en el objeto Freír_Bifes_Con_Cebolla, que tiene bifes como dato y saber freír bifes cómo método. Para el caso de Salsa de pimiento verde, Bife_Criollo le envía un mensaje diciéndole prepárate y entonces se preparará enviando mensajes a Ingredientes que los Rehoga, Licuadora que los pica y finalmente llama a Sartén para que los fría. Finalmente Bife_Criollo le va a enviar un mensaje a Acompañamiento, para que se añada la papa y el perejil, y quede finalmente preparado nuestro plato Bife Criollo. Es importante notar que los datos se encuentran implícitos formando parte de los objetos. Si en este modelo se desea hacer el Bife Criollo con Salsa de Pimientos Rojos, solo bastaría sustituir el objeto Salsa de Pimientos Verdes por el objeto Salsa de Pimientos Rojos, que se preparará, enviando mensajes a Ingredientes, Licuadora y Sartén, sin modificar nada más. 18 Capítulo 2 – Programación Orientada a Objetos – Fijando Conceptos. Capitulo 2. PROGRAMACIÓN ORIENTADA A OBJETOS – FIJANDO CONCEPTOS Introducción. La Orientación a Objetos (OO) promete mejoras de amplio alcance en la forma de diseño, desarrollo y mantenimiento del software proponiendo una solución a largo plazo a los problemas y preocupaciones que han existido desde el comienzo en el desarrollo de software: la falta de portabilidad del código y reusabilidad, código que es difícil de modificar, ciclos de desarrollo largos, y técnicas de codificación no intuitivas. Un lenguaje orientado a objetos que ataca estos problemas, tiene 3 características básicas: debe estar basado en objetos, debe estar basado en clases, y ser capaz de tener herencia de clases. Muchos lenguajes cumplen uno o dos de estos puntos; muchos menos cumplen los tres. La barrera más difícil de sortear es usualmente la herencia. Dado que la Programación Orientada a Objetos (POO) se basa en la idea natural de la existencia de un mundo lleno de objetos y que la resolución del problema se realiza en términos de objetos, un lenguaje se dice que está basado en objetos si soporta objetos como una característica fundamental del mismo. El elemento fundamental de la POO es, como su nombre lo indica, el objeto. Podemos definir un objeto como un conjunto de datos y programas que poseen estructura y forma parte de una organización. Esta definición especifica varias propiedades importantes de los objetos. En primer lugar, un objeto no es un dato simple, sino que contiene en su interior cierto número de componentes bien estructurados. En segundo lugar, cada objeto no es un ente aislado, sino que forma parte de una organización jerárquica o de otro tipo. 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. 19 Capítulo 2 – Programación Orientada a Objetos – Fijando Conceptos. 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. 20 Capítulo 2 – Programación Orientada a Objetos – Fijando Conceptos. 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 los 21 Capítulo 2 – Programación Orientada a Objetos – Fijando Conceptos. objetos 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: 22 Capítulo 2 – Programación Orientada a Objetos – Fijando Conceptos. 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. 23 Capítulo 2 – Programación Orientada a Objetos – Fijando Conceptos. 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. 24 Capítulo 2 – Programación Orientada a Objetos – Fijando Conceptos. 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. 25 Capítulo 2 – Programación Orientada a Objetos – Fijando Conceptos. 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 especialización o heredada. denominar subclase, descendiente, derivada, 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. 26 Capítulo 2 – Programación Orientada a Objetos – Fijando Conceptos. 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. 27 Capítulo 2 – Programación Orientada a Objetos – Fijando Conceptos. 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. 28 Capitulo 3. JAVA Y LA PROGRAMACIÓN ORIENTADA A OBJETOS Introducción. ¿Porqué Java? Java es un lenguaje puro Orientado a Objetos, es decir cumple perfectamente el paradigma de OO. Se ha elegido este lenguaje para ejemplificar los siguientes conceptos de PPO: Objetos Clase Modularidad Herencia Jerarquías Abstracción Encapsulación Ligaduras Dinámicas o Binding Mensajes. Polimorfismo o Sobrecarga Redefinición Es un lenguaje de programación relativamente joven, a pesar de ello, desde su aparición hasta nuestros días, el crecimiento de su uso ha sido vertiginoso. Esto se debe en gran parte a la naturaleza misma del lenguaje, que ha permitido a los creadores de Java añadir al lenguaje bibliotecas de clases que pueden ser fácilmente adaptadas a las necesidades del desarrollador de software. Sun Microsystems, es la empresa que ha desarrollado el lenguaje Java, en un intento de resolver simultáneamente todos los problemas que se le plantean a los desarrolladores de software por la proliferación de arquitecturas incompatibles, tanto entre las diferentes máquinas como entre los diversos sistemas operativos y sistemas de ventanas que funcionaban sobre una misma máquina, añadiendo la dificultad de crear aplicaciones distribuidas en una red como Internet. Origen de Java. Entre las “leyendas” más difundidas, respecto del origen del lenguaje Java, se encuentra aquella que dice que: En enero de 1991, James Gosling, Mike Sheridan, Chris Warth, Ed Frank y Patrick Naughton, entre otros, de Sun Microsystems, se reúnen y toman la idea de que el futuro de la Informática pasa porque todos los electrodomésticos y productos de consumo estén controlados por un ordenador. Muchos han colaborado en el diseño y evolución del lenguaje: Bill Joy, Arthur van Hoff, Jonathan Payne, Frank Yellin y Tim Lindholm, por citar algunos de ellos. Tras unos comienzos dudosos, Sun decidió crear una filial, denominada FirstPerson Inc., para dar margen de maniobra al equipo responsable del proyecto. 29 Capítulo 3 – Java y la Programación Orientada a Objetos. El mercado, inicialmente previsto para los programas de FirstPerson, eran los equipos domésticos: microondas, tostadoras y, fundamentalmente, televisión interactiva. Este mercado, dada la falta de pericia de los usuarios para el manejo de estos dispositivos, requería interfases mucho más cómodos e intuitivos que los sistemas de ventanas que proliferaban en el momento. Otros requisitos importantes a tener en cuenta eran la fiabilidad del código y la facilidad de desarrollo. James Gosling, el miembro del equipo con más experiencia en lenguajes de programación, decidió que las ventajas aportadas por la eficiencia de C++ no compensaban el gran coste de pruebas y depuración. James Gosling había estado trabajando en su tiempo libre en un lenguaje de programación que él había llamado Oak, el cual, aún partiendo de la sintaxis de C++, intentaba remediar las deficiencias que iba observando. Los lenguajes como C o C++, deben ser compilados para un chip, y si se cambia el chip, todo el software debe compilarse de nuevo. Esto encarece mucho los desarrollos y el problema es especialmente sentido en el campo de la electrónica de consumo. La aparición de un chip más barato y, generalmente, más eficiente, conduce inmediatamente a los fabricantes a incluirlo en las nuevas series de sus cadenas de producción, por pequeña que sea la diferencia en precio ya que, multiplicada por la tirada masiva de los aparatos, supone un ahorro considerable. Por tanto, Gosling decidió mejorar las características de Oak y utilizarlo. El primer proyecto en que se aplicó este lenguaje recibió el nombre de Proyecto Verde (Green Project) y consistía en un sistema de control completo de los aparatos electrónicos y el entorno de un hogar. Para ello se construyó un ordenador experimental denominado *7 (Star Seven), estamos hablando del año 1992. El sistema presentaba una interfaz basada en la representación de la casa de forma animada y el control se llevaba a cabo mediante una pantalla sensible al tacto. En el sistema aparecía Duke, la actual mascota de Java (esa especie de pingüinito de cabeza negra, cuerpo blanco y nariz roja). Posteriormente se aplicó a otro proyecto denominado VOD (Video On Demand) en el que se lo empleaba como interfaz para la televisión interactiva. Ninguno de estos proyectos se convirtió nunca en un sistema comercial, pero fueron desarrollados enteramente en un Java primitivo y fueron como su bautismo de fuego. En 1993, la compañía de TV, Time Warner, pretende introducirse en el mercado de la televisión interactiva. Sun y Silicon Graphics compiten para hacerse con el proyecto. Lo consigue Sun. Una vez que en Sun se dieron cuenta de que a corto plazo la televisión interactiva no iba a ser un gran éxito, urgieron a FirstPerson a desarrollar con rapidez nuevas estrategias que produjeran beneficios. No lo consiguieron y FirstPerson cerró en la primavera de 1994. Los miembros del equipo, al que pertenece James Gosling, pasan a Sun Interactive, otra filial de Sun creada en 1994. En paralelo, aparece en 1993 el navegador NCSA Mosaic, que facilitará el acceso interactivo a la Internet. El número de usuarios de la Internet crece exponencialmente. En 1994, Bill Joy decide escribir un navegador (WebRunner, tipo Mosaic) en código Oak, en principio, sólo como una demo, que se convertirá más adelante en HotJava en septiembre de dicho año. James Gosling escribe un compilador de Java. Hoffman escribe otro en Java. 30 Capítulo 3 – Java y la Programación Orientada a Objetos. En marzo de 1995, la versión “1.0a” sale al mundo exterior y el código fuente de Java se distribuye en Internet. Y bueno ...., a partir de allí el uso intensivo de Java no ha parado de crecer. Lo mejor será hacer caso omiso de las historias que pretenden dar carta de naturaleza a la clarividencia industrial de sus protagonistas; porque la cuestión es si, independientemente de su origen y entorno comercial, Java ofrece soluciones a nuestras expectativas. Porque tampoco vamos a desechar la penicilina aunque haya sido su origen fruto de la casualidad. Recomiendo leer en Internet, una muy completa historia de Java en la siguiente dirección: http://ei.cs.vt.edu/~wwwbtb/book/chap1/java_hist.html Características de Java Es importante ver las características del lenguaje Java, para decidir si lo utilizamos o no, según el tipo de trabajo que debemos encarar. Estas características seguramente son opinables según el punto de vista que se las analice. Algunas de ellas son: Tamaño reducido Simplicidad Portabilidad Interpretado Distribuido Robusto Seguro Programación concurrente y distribuida Dinámico Liberación de memoria no utilizada Manejo de Arreglos Manejo de excepciones Analicemos estas características individualmente: Reducido: Todo el paquete de desarrollo que incluye la librería de clases, los programas de compilación, interpretación, depuración, aplicaciones, Applets ejemplo, etc., ocupa aproximadamente 10 Mb en disco. Simple: Porque, prácticamente sin saber mucho de programación, se aprende a hacer Applets y aplicaciones fácilmente; pudiéndose obtener resultados en poco tiempo. Java ofrece toda la funcionalidad de un lenguaje 31 Capítulo 3 – Java y la Programación Orientada a Objetos. potente, pero sin las características menos usadas y más confusas de éstos. C++ es un lenguaje que adolece de falta de seguridad, pero es muy difundido, por ello, Java se diseñó para ser parecido a C++ y así facilitar un rápido y fácil aprendizaje. Java elimina muchas de las características de otros lenguajes como C++, para mantener reducidas las especificaciones del lenguaje y añadir características muy útiles como el reciclador de memoria dinámica (ver explicación detallada más abajo). Java reduce en un 50% los errores más comunes de programación con lenguajes como C y C++ al eliminar muchas de las características de éstos, entre las que destacan: a. aritmética de punteros b. no existen referencias c. registros (struct) d. definición de tipos (typedef) e. macros (#define) f. necesidad de liberar memoria (free) Portable, Interpretado: este era el principal objetivo que Sun buscaba, pues permite interpretar los programas Java desde cualquier plataforma de programación. Y es un lenguaje de plataforma independiente, tanto, al nivel de código fuente, como al nivel binario. Podemos escribir código Java en una plataforma y marcharnos a otra con la garantía de que ésta también entenderá el código fuente sin necesidad de tener que rescribirlo. Por otro lado, los archivos binarios Java resultado de la ejecución, conocidos por bytecodes, podrán ejecutarse desde cualquier plataforma sin necesidad de ninguna recompilación. Los bytecodes equivalen a código máquina pero sin ser específicos de ningún procesador. Esto es así porque para ejecutar los programas en Java se hacen dos operaciones: 1) la compilación obteniendo los bytecodes (usando el compilador javac.exe) y 2) la interpretación de estos desde cada plataforma con el intérprete Java (usando java.exe). El inconveniente del uso de bytecodes, como tenemos ocasión de ver cuando cargamos un Applet desde una página web, es la pérdida de velocidad de ejecución. Robusto, Seguro: permite la transferencia y el manejo de datos sin errores aparentes; siempre será posible la caída de la red o de nuestra maquina. Cada vez que se transfiere al ordenador de un usuario un programa, se corre el riesgo de recibir un virus, caballos de Troya, etc. Además, existen otros programas maliciosos contra los que hay que protegerse: estos programas pueden recoger información privada del usuario como números de tarjetas de crédito, cuentas bancarias y palabras 32 Capítulo 3 – Java y la Programación Orientada a Objetos. de acceso que pueden ser obtenidas analizando el contenido del sistema de ficheros local de la computadora cliente. Java establece un cortafuegos (firewall) entre una aplicación de red y la computadora local: cuando se carga desde el navegador de un usuario una página web que contiene un applet Java, no hay ningún peligro de ser infectados por un virus o a recibir intentos de accesos malintencionados. Java consigue esto creando un entorno de ejecución aislado para ese applet, que no tiene acceso a los recursos locales de la máquina cliente. Ésta es una de las grandes ventajas de Java, en cuanto a seguridad se refiere. Concurrente: permite ejecutar varios programas Java (applets) a la vez en el navegador. Java al ser multitareas (multithreaded), permite muchas actividades simultáneas en un programa. Los threads (a veces llamados, procesos ligeros o hilos), son básicamente pequeños procesos o piezas independientes de un gran proceso. Al estar los threads construidos en el lenguaje, son más fáciles de usar y más robustos que sus homólogos en C o C++. El beneficio de ser multithreaded consiste en un mejor rendimiento interactivo y mejor comportamiento en tiempo real. Aunque el comportamiento en tiempo real está limitado a las capacidades del sistema operativo subyacente (Unix, Windows, etc.), aún supera a los entornos de flujo único de programa (single-threaded), tanto en facilidad de desarrollo, como en rendimiento. Como ejemplo de todo esto, supongamos la visualización de una imagen desde Internet. Con Java, esta imagen se puede ir trayendo en un thread independiente, permitiendo que el usuario pueda acceder a la información en la página sin tener que esperar por el navegador. Distribuido: Java se ha construido con extensas capacidades de interconexión TCP/IP (Transmission Control Protocol/Internet Protocol). Existen librerías de rutinas para acceder e interactuar con protocolos como http y ftp. Esto permite a los programadores acceder a la información a través de la red con tanta facilidad como a los ficheros locales. Java en sí no es distribuído, sino que proporciona las librerías y herramientas para que los programas puedan ser distribuidos, es decir, que se ejecuten en varias máquinas, interactuando. Como ejemplo, Java también tiene la posibilidad de que los objetos puedan ejecutar procedimientos remotos; esta facilidad se llama RMI (Remote Method Invocation). Esto le aporta más características del modelo cliente/servidor. Dinámico: por su capacidad de interactuar con el usuario, Java se beneficia todo lo posible de la tecnología orientada a objetos. Java no intenta conectar todos los módulos que comprenden una aplicación hasta el tiempo de ejecución. Las librerías nuevas o actualizadas no paralizarán las aplicaciones actuales (siempre que mantengan el API (Application Programming Interface)). La siguiente figura muestra las diferencias entre un navegador convencional y uno con Java. 33 Capítulo 3 – Java y la Programación Orientada a Objetos. Java también simplifica el uso de protocolos nuevos o actualizados. Si nuestro ejecuta una aplicación Java sobre la red y encuentra una pieza de la aplicación que no sabe manejar, es capaz de traer automáticamente cualquiera de esas piezas que el sistema necesita para funcionar, desde el servidor remoto. Java, para evitar tener que ir descargando módulos de byte-codes, objetos o nuevas clases, implementa las opciones de persistencia, para que no se eliminen cuando se limpie la caché de la máquina. Administrador de Memoria: Es una tarea de la cual el programador no debe preocuparse. Java elimina muchas de las características de otros lenguajes como C++, para mantener reducidas las especificaciones del lenguaje y añadir características muy útiles como el garbage collector (reciclador de memoria dinámica). No es necesario preocuparse de liberar memoria, el reciclador se encarga de ello y como es un thread de baja prioridad, cuando entra en acción, permite liberar bloques de memoria muy grandes, lo que reduce la fragmentación de la memoria. Administrador de Arreglos: Posee un eficiente sistema de administración de arreglos (array), que permite almacenar gran cantidad de datos en 34 Capítulo 3 – Java y la Programación Orientada a Objetos. memoria, con un acceso directo a ellos. Implementa los arrays auténticos, en vez de listas enlazadas de punteros, con comprobación de límites, para evitar la posibilidad de sobreescribir o corromper memoria resultado de punteros que señalan a zonas equivocadas. Excepciones: Se dispone de una buena batería de métodos que controlan los posibles errores que pudiesen ocurrir durante la ejecución del programa, los que evitan, estando bien utilizados, que el programa cancele su ejecución. Una excepción es una condición anormal que surge en una secuencia de código durante la ejecución de un programa. Es decir, es un "error de ejecución". En los lenguajes de programación que no tienen gestión de excepciones, hay que controlar los posibles errores de ejecución del programa manualmente, usando códigos de error. Sin embargo, Java posee gestión de excepciones, y lleva el problema de la gestión del error en tiempo de ejecución al mundo orientado a objetos. En Java, cuando surge una condición excepcional, se crea un objeto que representa la excepción y se envía al método que provocó esta excepción. Este método puede elegir gestionar la excepción él mismo o pasarla. Pero en algún punto, la excepción es capturada y procesada. Las excepciones pueden ser generadas por el intérprete de Java o pueden ser generadas por el propio código. Las excepciones generadas por Java están relacionadas con errores que violan las reglas del lenguaje Java o las restricciones del entorno de ejecución de Java. Las excepciones generadas manualmente se suelen utilizar para informar de algún error al método llamante. 35 Capítulo 4 – Conceptos de Orientación a Objetos. Capitulo 4. PROFUNDIZANDO CONCEPTOS DE POO – EJEMPLOS CON JAVA En este capítulo se analizarán los siguientes conceptos de la POO, pero aplicados a ejemplos básicos utilizando el lenguaje de programación JAVA. 1. Herencia 2. Sobrescritura de métodos 3. La clase base y la clase derivada 4. Redefinición de Métodos Heredados 5. Clases y Métodos Abstractos 6. Sobrecarga/Polimorfismos 7. Ligaduras Dinámicas ó Binding 8. Redefinición 9. Interfases 10. Permisos de Accesos 11. Transformaciones de tipos ó Casting 12. Sincronización 1. Herencia. La herencia, concepto ya creado por Dahl y Nygaar en 1965, es una propiedad esencial de la Programación Orientada a Objetos que consiste en la creación de nuevas clases a partir de otras ya existentes Es el mecanismo mediante el cual un objeto adquiere (o hereda) las propiedades de otro. De esta forma se consigue la clasificación jerárquica. Si no se hiciera una clasificación jerárquica de los objetos, cada objeto debería definir todas sus características explícitamente, y esto no sería viable. Sin embargo, utilizando la herencia, un objeto puede heredar sus atributos generales de otro objeto (su padre), y definir explícitamente sólo aquellas cualidades que lo hacen único dentro de su clase. Por tanto, la herencia es el mecanismo que permite a un objeto ser una instancia específica de un caso más general. Una clase que hereda de otra, se denomina subclase, y aquella clase de la que se hereda, se denomina superclase. Por ejemplo, supongamos que se define una clase para describir a los animales; se podría definir otra clase para describir a los mamíferos. La clase "mamíferos" heredaría todos los atributos de la clase "animales", pero además definiría una serie de atributos específicos de los mamíferos, como el tipo de dientes, o las glándulas mamarias. En este caso, la clase "mamíferos" es subclase de "animales", y la clase "animales" es superclase de "mamíferos". Una subclase hereda todos los atributos de cada uno de sus antecesores en la jerarquía de clases. Esta característica permite a los programas orientados a objetos crecer en complejidad de manera lineal en vez de geométrica. 37 Capítulo 4 – Conceptos de Orientación a Objetos. La herencia es la característica fundamental que distingue un lenguaje orientado a objetos, como el C++, Smalltalk, Eiffel o Java, de otro convencional como C, BASIC, etc. Herencia en Java. La herencia permite las clasificaciones jerárquicas. En Java, para heredar una clase, se utiliza la palabra reservada extends. Por ejemplo, si la clase B hereda la clase A (B es subclase de A, A es superclase de B), la declaración de la clase B sería así: class B extends A { // variables de instancia // métodos } Así, la clase B hereda todas las variables de instancia y métodos de la clase A, y además define sus propias variables de instancia y métodos. Pero hay una excepción: una subclase no puede acceder a aquellos miembros de la superclase que han sido declarados como private. 2. Sobrescritura de métodos. En una jerarquía de clases, cuando un método de una subclase tiene el mismo nombre y tipo que un método de su superclase, entonces se dice que el método de la subclase sobrescribe al método de la superclase. Y cuando se llama a un método sobrescrito dentro de una subclase, siempre se refiere a la versión del método definida en la subclase. La versión del método definida por la superclase está oculta. Y la llamada a una función sobrescrita se resuelve en tiempo de ejecución, en lugar de durante la compilación; esto se denomina selección de método dinámica. La selección de método dinámica es la forma que tiene Java de implementar el polimorfismo durante la ejecución. Así, combinando la herencia con la sobrescritura de métodos, una superclase puede definir la forma general de los métodos que serán utilizados por todas sus subclases. Por otra parte, este polimorfismo dinámico es uno de los mecanismos más poderosos que ofrece el diseño orientado a objetos para soportar la reutilización del código y la robustez. 3. La clase base y la clase derivada. La herencia ofrece una ventaja importante, permite la reutilización del código. Una vez que una clase ha sido depurada y probada, el código fuente de dicha clase no necesita modificarse. Su funcionalidad se puede cambiar derivando una nueva clase que herede la funcionalidad de la clase base y le añada otros comportamientos. 38 Capítulo 4 – Conceptos de Orientación a Objetos. La programación en los entornos gráficos, en particular Windows, con el lenguaje C++, es un ejemplo ilustrativo. Los compiladores como los de Borland y Microsoft proporcionan bibliotecas de clases cuyas clases describen el aspecto y la conducta de las ventanas, controles, menús, etc. Una de estas clases denominada TWindow describe el aspecto y la conducta de una ventana, tiene una función miembro denominada Paint, que no dibuja nada en el área de trabajo de la misma. Definiendo una clase derivada de TWindow, podemos redefinir en ella la función Paint para que dibuje una figura. Aprovechamos de este modo la ingente cantidad y complejidad del código necesario para crear una ventana en un entorno gráfico. Solamente, tendremos que añadir en la clase derivada el código necesario para dibujar un rectángulo, una elipse, etc. La base de la POO fue este tipo de programación, en entornos de ventanas, de hecho SmallTalk se hizo para eso. En el lenguaje Java, todas las clases derivan implícitamente de la clase base Object, por lo que heredan las funciones miembro definidas en dicha clase. En Java disponemos de los paquetes básicos: java.lang: Clases propias del lenguaje, clase Object, clase String, clase System y clases para los tipos asociados a las primitivas. java.util: Clases para las utilidades, clase Date, clase Vector y clase Hashtable. java.io: Clases para la E/S y para el manejo de ficheros. java.net: Clases para el soporte de redes; clases Socket y clase URL. java.awt: Clases para la interfase gráfica, incluyendo las clases Window, Menu, Button, Font, CheckBox. java.applet: Clases que implementan las Applets de Java, incluyendo la clase Applet y la interfase AudioClip. La clase base. Algunas de las típicas ocasiones en las que nos vemos obligados a crear clases bases son, por ejemplo: En la fase de análisis, nos damos cuenta que diversos tipos de datos tienen algo en común, por ejemplo en el juego del ajedrez: peones, alfiles, rey, reina, caballos y torres, son piezas del juego. Creamos, por lo tanto, una clase base pieza y derivamos cada pieza individual a partir de dicha clase base. 39 Capítulo 4 – Conceptos de Orientación a Objetos. En otra ocasión, precisamos ampliar la funcionalidad de un programa, sin tener que modificar el código existente. Ejemplos de aplicación del presente concepto los vemos en los programas: Ventana.java, VentanaTitulos.java y VentanaApp.java. Vamos a poner un ejemplo del segundo tipo, (ampliar la funcionalidad de un programa) que simule la utilización de librerías de clases para crear una interfase gráfica de usuario como Windows 3.1 o Windows 95. Supongamos que tenemos una clase que describe la conducta de una ventana muy simple, aquella que no dispone de título en la parte superior, por lo tanto no puede desplazarse, pero si cambiar de tamaño actuando con el ratón en los bordes derecho e inferior. La clase Ventana tendrá las siguientes variables miembros: la posición x e y de la ventana, de su esquina superior izquierda, y las dimensiones de la ventana: ancho y alto. public class Ventana { protected int x; protected int y; protected int ancho; protected int alto; //constructor de la Clase Base } public Ventana(int x, int y, int ancho, int alto) { this.x=x; this.y=y; this.ancho=ancho; this.alto=alto; } Constructor (Ventana) Nombre del constructor = nombre de la clase Se encarga de todas las operaciones de inicialización necesarias. No tiene valor de retorno. Las funciones miembro, además del constructor serán las siguientes: la función mostrar que simula una ventana en un entorno gráfico, aquí solamente nos muestra la posición y las dimensiones de la ventana. public void mostrar(){ System.out.println("posición : x =" + x + ", y 40 Capítulo 4 – Conceptos de Orientación a Objetos. =" + y); System.out.println("dimensiones: ancho =" + ancho + ", alto =" + alto); } La función cambiarDimensiones (de forma relativa) que simula el cambio en la anchura y altura de la ventana. public void cambiarDimensiones(int dancho, int dalto){ ancho += dancho; alto += dalto; } Objetos de la clase base. Como vemos en el código, el constructor de la clase base inicializa los cuatro miembros dato. Llamamos al constructor creando un objeto de la clase Ventana (Java invoca al constructor al crear el objeto). La instanciación (new) reserva el lugar de almacenamiento e invoca al constructor Ventana ventana=new Ventana (0, 0, 20, 30); Desde el objeto ventana podemos llamar a las funciones miembro públicas ventana.mostrar(); ventana.cambiarDimensiones(10, 10); La clase derivada Podemos incrementar la funcionalidad de la clase Ventana definiendo una clase derivada denominada VentanaTitulo. Los objetos de dicha clase tendrán todas las características de los objetos de la clase base, pero además tendrán un título, y se podrán desplazar (se simula el desplazamiento de una ventana con el ratón). La clase derivada heredará las variables miembros de la clase base y las funciones miembro, y tendrá una variable miembro más, el título de la ventana. public class VentanaTitulo extends Ventana{ protected String titulo; public VentanaTitulo(int x, int y, int ancho, int alto, String nombre) { super(x, y, ancho, alto); titulo = nombre; } } extends es la palabra reservada que indica que la clase VentanaTitulo deriva, o es una subclase, de la clase Ventana. 41 Capítulo 4 – Conceptos de Orientación a Objetos. La primera sentencia del constructor de la clase derivada es una llamada al constructor de la clase base mediante la palabra reservada super. La llamada super(x, y, ancho, alto); inicializa los cuatro miembros dato de la clase base Ventana:( x, y, ancho, alto) A continuación, se inicializan las variables miembros de la clase derivada, y se realizan las tareas de inicialización que sean necesarias. Si no se llama explícitamente al constructor de la clase base Java lo realiza por nosotros, llamando al constructor por defecto si existe. La función miembro denominada desplazar cambia la posición de la ventana, añadiéndole el desplazamiento. public void desplazar(int dx, int dy){ x+=dx; y+=dy; } Ahora redefinimos la función miembro mostrar para mostrar una ventana con un título. public void mostrar(){ super.mostrar(); System.out.println("titulo } : "+titulo); En la clase derivada se define una función que tiene el mismo nombre y los mismos parámetros que la de la clase base. (redefinimos la función mostrar en la clase derivada). La función miembro mostrar de la clase derivada VentanaTitulo hace una llamada a la función mostrar de la clase base Ventana, mediante super.mostrar(); De este modo aprovechamos el código ya escrito, y le añadimos el código que describe la nueva funcionalidad de la ventana (por ejemplo, que muestre el título). Si nos olvidamos de poner la palabra reservada super llamando a la función mostrar, tendríamos una función recursiva. (La función mostrar llamaría a mostrar indefinidamente.) public void mostrar(){ //¡ojo!,función recursiva System.out.println("titulo : "+titulo); mostrar(); } Objetos de la clase derivada. 42 Capítulo 4 – Conceptos de Orientación a Objetos. Creamos un objeto ventana de la clase derivada VentanaTitulo VentanaTitulo ventana=new VentanaTitulo(0, 0, 20, 30, "Principal"); Mostramos la ventana con su título, llamando a la función mostrar, redefinida en la clase derivada ventana.mostrar(); Desde el objeto ventana de la clase derivada llamamos a las funciones miembro definidas en dicha clase ventana.desplazar(4, 3); Desde el objeto ventana de la clase derivada podemos llamar a las funciones miembro definidas en la clase base. ventana.cambiarDimensiones(10, -5); Para mostrar la nueva ventana desplazada y cambiada de tamaño escribimos ventana.mostrar(); Modificadores de acceso. En la herencia, surge un nuevo control de acceso denominado protected. Hemos puesto protected delante de las variables miembros x e y de la clase base Ventana public class Ventana { protected int x; protected int y; //... } En la clase derivada, la función miembro desplazar accede a dichas variables miembros public class VentanaTitulo extends Ventana{ //... public void desplazar(int dx, int dy){ x+=dx; y+=dy; } } 43 Capítulo 4 – Conceptos de Orientación a Objetos. Si cambiamos el modificador de acceso de los miembros x e y de la clase base Ventana de protected a private, el compilador nos informará que los miembros x e y no son accesibles. Los miembros ancho y alto se pueden poner con acceso private, sin embargo, es mejor dejarlos como protected ya que podrían ser utilizados por alguna función miembro de otra clase derivada de VentanaTitulo. Dentro de una jerarquía pondremos un miembro con acceso private, si estamos seguros de que dicho miembro solamente va a ser usado por dicha clase. Como vemos hay cuatro modificadores de acceso a los miembros dato y a los métodos: 1. private, las variables y métodos de instancia privados sólo pueden ser accedidos desde dentro de la clase. No son accesibles desde las subclases. 2. protected, sólo las subclases de la clase y nadie más, puede acceder a las variables y métodos de instancia protegidos 3. public, cualquier clase desde cualquier lugar puede acceder a las variables y métodos de instancia públicos 4. friendly, por defecto, si no se especifica el control de acceso, las variables y métodos de instancia se declaran friendly (amigas), lo que significa que son accesibles por todos los objetos dentro del mismo paquete, pero no por los externos al paquete. Es lo mismo que protected. Veamoslo resumido en los siguientes cuadros: Clases dentro del mismo paquete: Modificador de acceso Heredado Accesible Default Private Protected Public Si No Si Si Si No Si Si Modificador de acceso Heredado Accesible Default Private Protected Public No No Si Si No No No Si Clases en distintos paquetes: 44 Capítulo 4 – Conceptos de Orientación a Objetos. Desde el punto de vista práctico, cabe reseñar que no se heredan los miembros privados, ni aquellos miembros (dato o función) cuyo nombre sea el mismo en la clase base y en la clase derivada. La clase base object. La clase Object es la clase raíz de la cual derivan todas las clases. Esta derivación es implícita. La clase Object define una serie de funciones miembro que heredan todas las clases. Las más importantes son las siguientes: public class Object { public boolean equals(Object obj) { return (this == obj); } protected native Object clone() throws CloneNotSupportedException; public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); } protected void finalize() throws Throwable { } //otras funciones miembro... } Métodos que pueden ser redefinidos por el programador. Igualdad de dos Objetos: El método equals de la clase Object compara dos objetos uno que llama a la función y otro es el argumento de dicha función. equals() Indica si dos objetos son o no iguales. Devuelve true si son iguales, tanto si son referencias al mismo objeto como si son objetos distintos con iguales valores de las variables miembro. Entonces, boolean objeto.equals(Object), demuestra si el objeto dado como parámetro es igual al objeto actual. Esta implementación comprueba si ambas referencias son iguales. Podemos simplificar la consulta utilizando el operador = = (igualdad). Las distintas clases de la librería de Java suelen sobrescribirlo para comprobar si los contenidos de la instancia son los mismos. 45 Capítulo 4 – Conceptos de Orientación a Objetos. Por ejemplo, cuando String sobrescribe este método compara si las cadenas contenidas en ambas instancias son iguales. Representación en forma de texto de un objeto: El método toString() devuelve un String que contiene una representación del objeto como cadena de caracteres, por ejemplo para imprimirlo o exportarlo. El método toString imprime por defecto el nombre de la clase a la que pertenece el objeto y su código (hash). Esta función miembro se redefine en la clase derivada para mostrar la información que nos interese acerca del objeto. La función toString se llama automáticamente siempre que pongamos un objeto como argumento de la función System.out.println o concatenado con otro string. public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); } Entonces: String objeto.toString()devuelve una representación del objeto en formato de cadena de caracteres, es llamado automáticamente por Java cuando necesita convertir el objeto en cadena. Esta implementación devuelve una cadena que contiene el nombre de la clase del objeto, seguido de una arroba y del código hash del mismo. Duplicación de objetos: El método clone crea un objeto duplicado (clónico) de otro objeto, es decir, clone() crea un objeto a partir de otro objeto de la misma clase. El método original heredado de Object lanza una CloneNotSupportedException. Si se desea clonar una clase hay que implementar la interfase Cloneable y redefinir el método clone(). Este método debe hacer una copia miembro a miembro del objeto original. No debería llamar al operador new ni a los constructores. Entonces: Object objeto.clone() copia el objeto. La intención es que, al sobreescribirlo, creemos un objeto nuevo cuyas propiedades tengan el mismo valor que las del objeto a copiar. Es decir, que x.clone.equals(x) sea cierto. 46 Capítulo 4 – Conceptos de Orientación a Objetos. Finalización: El método finalize se llama cuando va a ser liberada la memoria que ocupa el objeto por el recolector de basura (garbage collector). Normalmente, no es necesario redefinir este método en las clases, solamente en contados casos especiales. Por ejemplo, si una clase mantiene un recurso que no es de Java como un descriptor de archivo o un tipo de letra del sistema de ventanas, o cuando se han abierto varios archivos durante la vida de un objeto, y se desea que los archivos estén cerrados cuando dicho objeto desaparece, entonces sería acertado el utilizar la finalización para asegurar que los recursos se liberan. Es similar a los destructores de C++. La forma en la que se redefine este método es el siguiente. class CualquierClase{ //.. protected void finalize() trows Throwable{ super.finalize(); //código que libera recursos externos } } La primera sentencia que contenga la redefinición de finalize ha de ser una llamada a la función del mismo nombre de la clase base, y a continuación le añadimos cierta funcionalidad, habitualmente, la liberación de recursos, cerrar un archivo, etc. Entonces: void objeto.finalize()es llamado por el recolector de basura antes de eliminar el objeto. Esta implementación no hace nada, debe ser el programador el que sobreescriba este método en caso de que quiera realizar algo en especial antes de eliminar el objeto de la memoria. Métodos que NO pueden ser Redefinidos: getClass() devuelve un objeto de la clase class, al cual se le pueden aplicar métodos para determinar el nombre de la clase, su superclase, las interfases implementadas, etc. Se puede crear un objeto de la misma clase que otro sin saber de qué clase es. Entonces: Class objeto.getClass() devuelve un objeto de tipo class que identifica el tipo de objeto que tenemos. Por ejemplo podemos comprobar si x e y son del mismo tipo con: x.getClass() = = y.getClass() int objeto.hashCode() devuelve el código Hash del objeto. Este código se utiliza para las tablas Hash, que son la manera más eficiente de almacenar objetos. Para que dichas tablas funcionen necesitan que cada objeto almacenado tenga un código numérico que se mantenga durante toda la ejecución del 47 Capítulo 4 – Conceptos de Orientación a Objetos. programa y que sea el mismo para dos objetos si dichos objeto son iguales según el método equals(). Un ejemplo sería, para las cadenas, devolver la longitud de la misma. notify(), notifyAll() wait() Son métodos relacionados con las threads Concretando: Se puede construir una clase a partir de otra mediante el mecanismo de la herencia. Para indicar que una clase deriva de otra se utiliza la palabra extends, class ClaseHija extends ClasePadre { ... } Cuando una clase deriva de otra, hereda todas sus variables y métodos. Estas funciones y variables miembro pueden ser redefinidas (overridden) en la clase derivada, que puede también definir o añadir nuevas variables y métodos. En cierta forma es como si la sub-clase (la clase derivada) “contuviera” un objeto de la super-clase; en realidad lo “amplía” con nuevas variables y métodos. Java permite múltiples niveles de herencia, pero no permite que una clase derive de varias (no es posible la herencia múltiple). Se pueden crear tantas clases derivadas de una misma clase como se quiera. Todas las clases de Java creadas por el programador tienen una super-clase. Cuando no se indica explícitamente una super-clase con la palabra extends, la clase deriva de java.lang.Object, que es la clase raíz de toda la jerarquía de clases de Java. Como consecuencia, todas las clases tienen algunos métodos que han heredado de Object. 4. Redefinición de métodos heredados Una clase puede redefinir cualquiera de los métodos heredados de su super-clase que no sean final. El nuevo método sustituye al heredado para todos los efectos en la clase que lo ha redefinido. Los métodos de la super-clase que han sido redefinidos pueden ser todavía accedidos por medio de la palabra super desde los métodos de la clase derivada, aunque con este sistema sólo se puede subir un nivel en la jerarquía de clases. 48 Capítulo 4 – Conceptos de Orientación a Objetos. Los métodos redefinidos pueden ampliar los derechos de acceso de la super-clase (por ejemplo ser public, en vez de protected o package), pero nunca restringirlos. Los métodos de clase static no pueden ser redefinidos en las clases derivadas. Modificador final: Si declaramos un método como final, indicaremos que no puede ser sobreescrito en clases heredadas, por lo que mantendrá su implementación. Si es la clase la que lleva el modificador (final), entonces nos aseguramos que nadie pueda heredar de dicha clase Por último, si declaramos una propiedad como final, crearemos una constante, ya que dicha variable se puede inicializar, pero no modificar. Este último caso presenta un uso más habitual de este modificador: class Dias { static final int LUNES=0, MARTES=1, MIERCOLES=2, JUEVES=3, VIERNES=4, SABADO=5, DOMINGO=6; } 5. Clases y métodos abstractos Una clase abstracta (abstract) es una clase de la que no se pueden crear objetos. Su utilidad es permitir que otras clases deriven de ella, proporcionándoles un marco o modelo que deben seguir y algunos métodos de utilidad general. Las clases abstractas se declaran anteponiéndoles la palabra abstract, por ejemplo: public abstract class NombreClase { ... } Una clase abstract puede tener métodos declarados como abstract, en cuyo caso no se da definición del método. Si una clase tiene algún método abstract es obligatorio que la clase sea abstract. En cualquier sub-clase este método deberá ser redefinido o volver a declararse como abstract (el método y la sub-clase). Una clase abstract puede tener métodos que no son abstract. Aunque no se puedan crear objetos de esta clase, sus sub-clases heredarán el método completamente a punto para ser utilizado. Debemos tener en cuenta que como los métodos static no pueden ser redefinidos, un método abstract no puede ser static. 6. Polimorfismo Esta palabra que significa "muchas formas", es una característica del lenguaje Java que permite a una interfase ser usada por una clase general de acciones. En 49 Capítulo 4 – Conceptos de Orientación a Objetos. términos más generales, el concepto de polimorfismo a menudo se expresa por la frase "una interfaz, múltiples métodos". Esto significa que es posible diseñar una interfaz genérica para un grupo de actividades relacionadas. Es evidente que esta forma de trabajar ayuda a reducir la complejidad del diseño, pues permite usar una misma interfaz para especificar un conjunto de acciones similares. Será el compilador el que tendrá que seleccionar la acción concreta (esto es, el método) para aplicar en cada situación. Como programadores, nosotros sólo tenemos que conocer la interfaz general. Más detalladamente, funciona así: cuando se llama a un método, el código generado por el compilador consulta una tabla oculta de punteros a funciones que dispone cada clase. La dirección de la función llamada no se asigna en tiempo de compilación como ocurre con el enlace estático del C sino que se obtiene la dirección de la función llamada justo en el momento antes de ejecutarse. Repasando, el polimorfismo consiste en poder definir métodos con la misma firma (nombre del método más argumentos) en diferentes niveles de la jerarquía de clases, y, que durante la ejecución el programa, sepa discernir a qué método llamar en cada caso. Podemos entender el sobrepaso fácilmente con un modelo simplificado. Durante la ejecución, cuando un mensaje es pasado a un objeto, la firma del mensaje es comparada con la firma de los mensajes de la clase a la que pertenece dicho objeto. Si se encuentra una igualdad el método correspondiente es ejecutado. Si no la firma del mensaje es comparada con la firma de los mensajes de la superclase. El proceso se repite hasta que una igualdad se encuentre. Una igualdad es garantizada, ya que el compilador de Java hubiera generado un error en el tiempo de compilación si la firma del mensaje invocado sobre un objeto no corresponde a ninguna firma dentro de la clase a la que pertenece este o alguna superclase. La idea básica es que una referencia a un objeto de una determinada clase es capaz de servir de referencia o de nombre a objetos de cualquiera de sus clases derivadas. ClasePadre objeto1, objeto2; objeto1= new ClaseHija(parámetros); objeto2= new ClaseHija2(parámetros); Análogamente una referencia a un objeto de una determinada Interfase es capaz de servir de referencia o de nombre a objetos de cualquiera de las clases que implementan dicha interfase. Interface objeto1, objeto2; objeto1= new Clase (parámetros); objeto2= new Clase2 (parámetros); Es decir, permite tratar de un modo unificado objetos distintos, aunque pertenecientes a distintas sub-clases o bien a clases que implementan dicha interfase. 50 Capítulo 4 – Conceptos de Orientación a Objetos. El polimorfismo tiene que ver con la relación que se establece entre la llamada a un método y el código que efectivamente se asocia con dicha llamada. A esta relación se llama vinculación o ligadura (binding). 7. Vinculación, Ligadura o Binding. La vinculación puede ser: temprana (en tiempo de compilación) tardía (en tiempo de ejecución). Con funciones normales o sobrecargadas se utiliza generalmente vinculación temprana. Con funciones redefinidas se utiliza siempre vinculación tardía, excepto si el método es final. La vinculación tardía hace posible que, con un método declarado en una clase base (o en una interfase) y redefinido en las clases derivadas (o en clases que implementan esa interfase), sea el tipo de objeto y no el tipo de la referencia lo que determine qué definición del método se va a utilizar. El tipo del objeto al que apunta una referencia sólo puede conocerse en tiempo de ejecución, y por eso el polimorfismo necesita evaluación tardía. El polimorfismo permite a los programadores separar las cosas que cambian de las que no cambian, y de esta manera hacer más fácil la ampliación, el mantenimiento y la reutilización de los programas. El polimorfismo puede hacerse con referencias de super-clases abstractas, superclases normales, e interfases. Las interfases permiten ampliar muchísimo las posibilidades del polimorfismo por su mayor flexibilidad y por su independencia de la jerarquía de clases estándar. Conversión de objetos El polimorfismo visto previamente está basado en utilizar referencias de un tipo más “amplio” que los objetos a los que apuntan. Las ventajas del polimorfismo son evidentes, pero hay una importante limitación: el tipo de la referencia (clase abstracta, clase base o interfase) limita los métodos que se pueden utilizar y las variables miembro a las que se pueden acceder. Por ejemplo, un objeto puede tener una referencia cuyo tipo sea una interfase, aunque sólo en el caso en que su clase o una de sus super-clases implementen dicha interfase. Un objeto cuya referencia es un tipo interfase sólo puede utilizar los métodos definidos en dicha interfase. Dicho de otro modo, ese objeto no puede utilizar las variables y los métodos propios de su clase. De esta forma las referencias de tipo interfase definen, limitan y unifican la forma de utilizarse de objetos pertenecientes a clases muy distintas (que implementan dicha interfase). Si se desea utilizar todos los métodos y acceder a todas las variables que la clase de un objeto permite, hay que utilizar un cast (lanzamiento) explícito, que convierta su 51 Capítulo 4 – Conceptos de Orientación a Objetos. referencia más general en la del tipo específico del objeto. De aquí una parte importante del interés del cast entre objetos (más bien entre referencias). Para la conversión entre objetos de distintas clases, Java exige que dichas clases estén relacionadas por herencia (una deberá ser subclase de la otra). Se realiza una conversión implícita o automática de una subclase a una superclase siempre que se necesite, ya que el objeto de la subclase siempre tiene toda la información necesaria para ser utilizado en lugar de un objeto de la superclase. La conversión en sentido contrario debe hacerse de modo explícito y puede producir errores por falta de información o de métodos. Si falta información, se obtiene una ClassCastException. No se puede acceder a las variables exclusivas de la sub-clase a través de una referencia de la super-clase. Sólo se pueden utilizar los métodos definidos en la super-clase, aunque la definición utilizada para dichos métodos sea la de la sub-clase. Por ejemplo, supóngase que se crea un objeto de una sub-clase B y se referencia con un nombre de una super-clase A, A a = new B(); en este caso el objeto creado dispone de más información de la que la referencia a le permite acceder (podría ser, por ejemplo, una nueva variable miembro j declarada en B). Para acceder a esta información adicional hay que hacer un cast explícito en la forma (B)a. System.out.println( ((B)a).j ); //imprime la variable j Un cast de un objeto a la super-clase puede permitir utilizar variables -no métodosde la super-clase, aunque estén redefinidos en la sub-clase. Considérese el siguiente ejemplo: La clase C deriva de B y B deriva de A. Las tres definen una variable x. En este caso, si desde el código de la sub-clase C se utiliza: x this.x super.x ((B)this).x ((A)this).x // // // // // // se accede a la se accede a la se accede a la subir un nivel se accede a la se accede a la x de C x de C x de B. Sólo se puede x de B x de A Métodos (funciones miembro). Los métodos son funciones definidas dentro de una clase. Salvo los métodos static o de clase, se aplican siempre a un objeto de la clase por medio del operador punto (.). Dicho objeto es su argumento implícito. Los métodos pueden además tener otros argumentos explícitos que van entre paréntesis, a continuación del nombre del método. 52 Capítulo 4 – Conceptos de Orientación a Objetos. La primera línea de la definición de un método se llama declaración (header), y el código comprendido entre las llaves {…} es el cuerpo (body) del método. public Float método() { // header y comienzo del método sentencias // body o cuerpo return valor } // final del método El header consta del cualificador de acceso (public, en este caso), del tipo del valor de retorno (Float en este ejemplo, void si no tiene), del nombre de la función y de una lista de argumentos explícitos entre paréntesis, separados por comas. Si no hay argumentos explícitos se dejan los paréntesis vacíos. Los métodos tienen visibilidad directa de las variables miembro del objeto que es su argumento implícito, es decir, pueden acceder a ellas sin cualificarlas con un nombre de objeto y el operador punto. De todas formas, también se puede acceder a ellas mediante la referencia this (this.variable) o sí, alguna variable local o argumento las oculta. El valor de retorno (return) puede ser un valor de un tipo primitivo o una referencia. En cualquier caso no puede haber más que un único valor de retorno (que puede ser un objeto o un arreglo). Se puede devolver también una referencia a un objeto por medio de un nombre de interfase. El objeto devuelto debe pertenecer a una clase que implemente esa interfase. Se puede devolver como valor de retorno un objeto de la misma clase que el método o de una sub-clase, pero nunca de una superclase. Los métodos pueden definir variables locales. Su visibilidad llega desde la definición al final del bloque en el que han sido definidas. No hace falta inicializar las variables locales en el punto en que se definen, pero el compilador no permite utilizarlas sin haberles dado un valor. A diferencia de las variables miembro, las variables locales no se inicializan por defecto. Si en el header del método se incluye la palabra native public native void metodo(); no hay que incluir el código o implementación del método. Este código deberá estar en una librería dinámica (Dynamic Link Library o DLL). Estas librerías son ficheros de funciones compiladas normalmente en lenguajes distintos de Java (C, C++, Fortran, etc.). Es la forma de poder utilizar conjuntamente funciones realizadas en otros lenguajes desde código escrito en Java. Un método también puede declararse como synchronized public synchronized double metodo(){...} 53 Capítulo 4 – Conceptos de Orientación a Objetos. Estos métodos tienen la particularidad de que sobre un objeto no pueden ejecutarse simultáneamente dos métodos que estén sincronizados. Métodos sobrecargados (overloaded) Otra de las ventajas de este lenguaje de programación es que nos permite definir dos o más métodos dentro de la misma clase con el mismo nombre, siempre que la declaración de sus parámetros sea diferente. En este caso se dice que el método está sobrecargado y el proceso de definir un método así se conoce como sobrecarga del método. La sobrecarga de métodos es una de las maneras en que Java implementa el polimorfismo. Cuando se llama a un método sobrecargado, el compilador actúa justo sobre la versión cuyo tipo de parámetros coincida con los de la llamada. Así, se podría definir la siguiente clase "SumaGenerica" que aglutinará las sumas de todos los tipos primitivos: class SumaGenerica { int suma (int a, int b) { return a+b; } double suma (double a, double b) { return a+b; } ... } Realmente no sería necesario definir el método suma() para todos los tipos de datos, pues aquí también interviene el casting implícito que hace Java. Por ejemplo, una suma de float, llamaría automáticamente al método que devuelve double siempre y cuando no esté definido el método que devuelve float, claro está. No existe una regla exacta para saber si un método se debe sobrecargar o no. Realmente, la idea es aprovechar las ventajas que ofrece esta forma de polimorfismo, así que lo normal es sobrecargar aquellos métodos que estén intrínsicamente relacionados, como es el caso del ejemplo anterior pero no nos debemos confundir. Por ejemplo, el método sqrt(), aunque se llama igual, calcula de forma totalmente diferente la raíz cuadrada de un número entero que la de uno de punto flotante. Aquí, aunque aplicáramos sobrecarga al método, realmente no estaríamos respetando el propósito para el que se creó el polimorfismo. Hemos expuesto que al igual que C++, Java permite métodos sobrecargados (overloaded), es decir, métodos distintos que tienen el mismo nombre, pero que se diferencian por el número y/o tipo de los argumentos. A la hora de llamar a un método sobrecargado, Java sigue las siguientes reglas para determinar el método concreto que debe llamar: 1. Si existe el método cuyos argumentos se ajustan exactamente al tipo de los argumentos de la llamada (argumentos actuales), se llama ese método. 54 Capítulo 4 – Conceptos de Orientación a Objetos. 2. Si no existe un método que se ajuste exactamente, se intenta promover los argumentos actuales al tipo inmediatamente superior (por ejemplo char a int, int a long, float a double, etc.) y se llama el método correspondiente. 3. Si sólo existen métodos con argumentos de un tipo más restringido (por ejemplo, int en vez de long), el programador debe hacer un cast explícito en la llamada. 4. El valor de retorno no influye en la elección del método sobrecargado. En realidad es imposible saber desde el propio método lo que se va a hacer con él. No es posible crear dos métodos sobrecargados, es decir con el mismo nombre, que sólo difieran en el valor de retorno. 8. Redefinición Una clase puede redefinir (override) un método heredado de una superclase. Redefinir un método es dar una nueva definición. En este caso el método debe tener exactamente los mismos argumentos en tipo y número que el método redefinido. Una sugerencia importante es que la redefinición debe ser algo excepcional en POO. Sobrecarga de Constructores Es aquí donde realmente se aprecian los beneficios del polimorfismo. Como sabemos, el constructor de una clase es el que inicializa los valores que el programador crea conveniente cuando ésta (la clase) se instancia. Pues bien, sobrecargando el constructor conseguimos dotar a la clase de flexibilidad. Por ejemplo, como mínimo se debería tener en cuenta que podría no pasársele parámetros al constructor, cuando éste lo espera, debido a un fallo en alguna otra parte de la aplicación (me refiero a cualquier otra clase que llame a ésta). Es por ello que siempre es recomendable definir al menos dos constructores: el específico de la aplicación que estemos diseñando y el "estándar". Veamos un ejemplo: class Caja { double ancho; double alto; double profundidad; // El siguiente es el constructor específico Caja(double w, double h, double d) { ancho = w; alto = h; profundidad = d; } // pero podría ser que no le llegarán parámetros // por fallar la otra clase (método) que lo // invoque 55 Capítulo 4 – Conceptos de Orientación a Objetos. Caja () { ancho = alto = profundidad = -1 //-1 indica volumen no existente } // e incluso podemos pensar que se quiere construir // un cubo, entonces, por qué introducir 3 valores? Caja (double valor) { ancho = alto = profundidad = valor; } } double volume() { return ancho * alto * profundidad; } Objetos como parámetros Además de usar los tipos primitivos como parámetros, en Java es perfectamente posible pasar a un método, un objeto como parámetro. class Comparar { int a, b; Comparar (int i, int j) { a = i; b = j; } } boolean equals (Comparar c) { if (c.a == a && c.b == b) return true; else return false; } Como se puede apreciar, el método equals() de Comparar comprueba si dos objetos son idénticos. Paso de argumentos. Existen dos formas de pasar un argumento a una rutina: Por valor: El método copia el valor de un argumento en el parámetro formal de la rutina. Por referencia: El método copia el objeto en el parámetro formal de la rutina. 56 Capítulo 4 – Conceptos de Orientación a Objetos. En el primer caso, al ser solo una copia, cualquier modificación dentro de la rutina de ese valor no tendrá efecto una vez fuera de éste, mientras que si es por referencia, sí persistirá la modificación hecha, aún cuando salgamos de la rutina. Cuando en Java se pasan argumentos de tipo simple, estos siempre se hacen por valor. Si se desea pasar un parámetro por referencia se debe pasar un objeto. Esto ocurre porque cuando se crea una variable de un tipo de clase, el programador solo crea una referencia al objeto. Así, cuando se pasa esta referencia a un método, el parámetro que recibe éste, se refiere al mismo objeto que el referido por el argumento. Por lo tanto, los cambios que se hagan en la referencia de la rutina afectarán también al objeto pasado como argumento. El operador static Hay veces que se desea definir una clase miembro para ser usada independientemente de cualquier objeto de esa clase. Normalmente a una clase miembro se accede sólo si hemos instanciado un objeto de dicha clase. No obstante, es posible crear un miembro que pueda ser usado por sí mismo, sin necesidad de referenciar a una instancia específica. Para crear tales tipos de miembros se emplea el operador static. Cuando un miembro se declara con esta palabra reservada, se puede acceder a él antes de que cualquier objeto de su clase sea creado, y sin referenciar a ningún objeto. Se puede declarar tanto los métodos como las variables como static. El ejemplo más claro de un miembro static es el main(). Se declara de esta manera porque se debe llamar antes de que cualquier objeto sea declarado. Las variables static Las variables de instancia declaradas como static (también llamadas "de clase") son, esencialmente, variables globales. Cuando creamos objetos específicos de esa clase no se hace ninguna copia de las variables static. Lo que ocurre es que todas las instancias de esa clase comparten la misma variable static. Estas variables mayormente tienen sentido cuando varias instancias de la misma clase necesitan llevar el control o estado del valor de un dato. Para llamar a este tipo de variables se suele utilizar el nombre de la clase (no es imprescindible, pues se puede utilizar también el nombre de cualquier objeto), porque de esta forma queda más claro, seguida de un "." y la variable Estatica.b Las variables miembro static se crean en el momento en que pueden ser necesarias: cuando se va a crear el primer objeto de la clase cuando se llama a un método static cuando se utiliza una variable static de dicha clase. Lo importante es que las variables miembro static se inicializan siempre antes que cualquier objeto de la clase. 57 Capítulo 4 – Conceptos de Orientación a Objetos. Los métodos static. También llamados "de clase", pueden recibir objetos de su clase como argumentos explícitos, pero no tienen argumento implícito ni pueden utilizar la referencia this. Para llamar a estos métodos se suele emplear el nombre de la clase, en vez del nombre de un objeto de la clase. Los métodos y variables de clase son lo más parecido que Java tiene a las funciones y variables globales de C/C++. Las restricciones que tiene un método static son: Solo pueden llamar otro método static Solo deben acceder a datos static No se pueden referir a this o super de ninguna manera Si se necesita hacer algún tipo de computación para inicializar las variables static, se puede declarar también un bloque static el cual se ejecutará solo una vez, cuando se carga. class Estatica { static int a = 3; static int b; static void show(int x) { System.out.println("x = " + x); System.out.println("a = " + a); System.out.println("b = " + b); } static { System.out.println("Bloque estático inicializado"); b = a * 4; // vemos que, aunque declarada como static // b se inicializa en tiempo de ejecución } public static void main(String args[ ]) { show(15); } } Tan pronto como la clase “Estatica” se carga, todas las sentencias se ejecutan. Primero, “a” se inicializa a 3, luego se ejecuta el bloque estático y, por último, “b” se inicializa al valor asignado. En resumen podemos decir para los miembros de tipo static: Miembros (métodos o atributos) implementados al nivel de clase Desde métodos static la referencia this no tiene sentido No se puede acceder a miembros no estáticos desde métodos estáticos static: Semántica de "ámbito global" Permite desarrollo de código sin usar POO 58 Capítulo 4 – Conceptos de Orientación a Objetos. El operador final Al declarar una variable como final, se previene que su contenido sea modificado. No obstante, Java permite separar la definición de la inicialización. Esta última se puede hacer más tarde, en tiempo de ejecución, llamando a métodos o en función de otros datos. La variable final así definida, es constante pero no tiene porqué tener el mismo valor en todas las ejecuciones del programa, pues depende de cómo haya sido inicializada. Esto es lo más parecido a las constantes de otros lenguajes de programación. final float PI = 3.1416; //Según la convención de // código las variables "constantes" se ponen en // mayúsculas. Aunque básicamente este operador se emplea para crear constantes en Java, también podemos definir una clase o un método como final. Una clase final no puede tener clases derivadas. Esto se hace por motivos de seguridad y de eficiencia, porque cuando el compilador detecta que los métodos no van a ser redefinidos puede hacer optimizaciones adicionales. Un método final no puede ser redefinido por una clase que derive de su propia clase. 9. Interfases. Una interfase es un conjunto de declaraciones de métodos (sin definición). También puede definir constantes, que son implícitamente public, static y final, y deben siempre inicializarse en la declaración. Todas las clases que implementan una determinada interfase están obligadas a proporcionar una definición de los métodos de la interfase, y en ese sentido adquieren una conducta o modo de funcionamiento. Una clase puede implementar una o varias interfases. Para indicar que una clase implementa una o más interfases se ponen los nombres de las interfases, separados por comas, detrás de la palabra implements, que a su vez va siempre a la derecha del nombre de la clase o del nombre de la super-clase en el caso de herencia. public class Clase extends ClasePadre implements Interfase, Interfase2 { ... ... } ¿Qué diferencia hay entre una interfase y una clase abstract? Ambas tienen en común que pueden contener varias declaraciones de métodos (la clase abstract puede además definirlos). A pesar de esta semejanza, que hace que en 59 Capítulo 4 – Conceptos de Orientación a Objetos. algunas ocasiones se pueda sustituir una por otra, existen también algunas diferencias importantes: Una clase no puede heredar de dos clases abstract, pero sí puede heredar de una clase abstract e implementar una interfase, o bien implementar dos o más interfases. Una clase no puede heredar métodos -definidos- de una interfase, aunque sí constantes. Las interfases permiten mucha más flexibilidad para conseguir que dos clases tengan el mismo comportamiento, independientemente de su situación en la jerarquía de clases de Java. Las interfases permiten “publicar” el comportamiento de una clase develando un mínimo de información. Las interfases tienen una jerarquía propia, independiente y más flexible que la de las clases, ya que tienen permitida la herencia múltiple. De cara al polimorfismo las referencias de un tipo interfase se pueden utilizar de modo similar a las clases abstract. Definición de interfases. Una interfase se define de un modo muy similar a las clases: public interface NombreInterface { public void metodo(param, param2); public void metodo2(param); } Cada interfase public debe ser definida en un fichero *.java con el mismo nombre de la interfase. Los nombres de las interfases suelen comenzar también con mayúscula. Las interfases no admiten más que los modificadores de acceso public y package. Si la interfase no es public no será accesible desde fuera del package. Los métodos declarados en una interfase son siempre package y abstract, de modo implícito. Utilización de interfases Las constantes definidas en una interfase se pueden utilizar en cualquier clase (aunque no implemente la interfase) precediéndolas del nombre de la interfase area = 2.0*NombreInterface.var*r; 60 Capítulo 4 – Conceptos de Orientación a Objetos. Sin embargo, en las clases que implementan la interfase las constantes se pueden utilizar directamente, como si fueran constantes de la clase. A veces se crean interfases para agrupar constantes simbólicas relacionadas. Desde el punto de vista del polimorfismo, el nombre de una interfase se puede utilizar como un nuevo tipo de referencia. En este sentido, el nombre de una interfase puede ser utilizado en lugar del nombre de cualquier clase que la implemente, aunque su uso estará restringido a los métodos de la interfase. Un objeto de ese tipo puede también ser utilizado como valor de retorno o como argumento de un método. Conceptos Útiles Es importante distinguir entre la referencia a un objeto y el objeto mismo. Los objetos son los elementos declarados de una clase. Una referencia es una variable que indica dónde está guardado un objeto en la memoria del ordenador La llamada a super. Referencia a la superclase de la que desciende la clase actual Reutilización de código por medio de herencia o Super invoca al comportamiento anterior. o Se puede añadir comportamiento adicional Implícita en constructores como primera instrucción En métodos: super.nombre_metodo() 10. Permisos de acceso en java. Una de las características de la POO es la encapsulación, que consiste básicamente en ocultar la información que no es pertinente o necesaria para realizar una determinada tarea. Los permisos de acceso de Java son una de las herramientas para conseguir esta finalidad. Accesibilidad de los Packages (paquetes). El primer tipo de accesibilidad hace referencia a la conexión física de las computadoras y a los permisos de acceso entre ellas y en sus directorios y ficheros. En este sentido, un paquete (package) es accesible si sus directorios y ficheros son accesibles (si están en un ordenador accesible y se tiene permiso de lectura). Además de la propia conexión física, serán accesibles aquellos paquetes que se encuentren en la variable CLASSPATH del sistema. Accesibilidad de Clases o Interfases. En principio, cualquier clase o interfase de un paquete es accesible para todas las demás clases del paquete, tanto si es public como si no lo es. 61 Capítulo 4 – Conceptos de Orientación a Objetos. Una clase public es accesible para cualquier otra clase (siempre que su paquete sea accesible). Recuérdese que las clases e interfases sólo pueden ser public o paquete (la opción por defecto cuando no se pone ningún modificador). Accesibilidad de las Variables y Métodos Miembros de una Clase. Desde dentro de la propia clase: Todos los miembros de una clase son directamente accesibles (sin calificar con ningún nombre o calificando con la referencia this) desde dentro de la propia clase. Los métodos no necesitan que las variables miembro sean pasadas como argumento. Los miembros private de una clase sólo son accesibles para la propia clase. Si el constructor de una clase es private, sólo un método static de la propia clase puede crear objetos. Desde una sub-clase. Las sub-clases heredan los miembros private de su super-clase, pero sólo pueden acceder a ellos a través de métodos public, protected o package de la super-clase. Desde otras clases del paquete (package). Desde una clase de un package se tiene acceso a todos los miembros que no sean private de las demás clases del package. Desde otras clases fuera del paquete (package). Los métodos y variables son accesibles si la clase es public y el miembro es public. También son accesibles si la clase que accede es una sub-clase y el miembro es protected. Resumen de los permisos de acceso de Java. Visibilidad Desde la propia clase Desde otra clase en el propio package Desde otra clase fuera del package Desde una sub-clase en el propio package Desde una sub-clase fuera del propio package public Si Si protected Si Si private Si No package Si Si Sí No No No Sí Sí No Si Si Si No No 62 Capítulo 4 – Conceptos de Orientación a Objetos. 11. Transformaciones de tipo: casting. En muchas ocasiones hay que transformar una variable de un tipo a otro, por ejemplo de int a double, o de float a long. En otras ocasiones la conversión debe hacerse entre objetos de clases diferentes, aunque relacionadas mediante la herencia. Explicaremos ahora estas transformaciones de tipo. Conversión de tipos primitivos. Casting Implícito: La conversión entre tipos primitivos es más sencilla. En Java se realizan de modo automático conversiones implícitas de un tipo a otro de más precisión, por ejemplo de int a long, de float a double, etc. Estas conversiones se hacen al mezclar variables de distintos tipos en expresiones matemáticas o al ejecutar sentencias de asignación en las que el miembro izquierdo tiene un tipo distinto (más amplio) que el resultado de evaluar el miembro derecho. Casting Explícito (Operador Cast): Las conversiones de un tipo de mayor a otro de menor precisión requieren una orden explícita del programador, pues son conversiones inseguras que pueden dar lugar a errores (por ejemplo, para pasar a short un número almacenado como int, hay que estar seguro de que puede ser representado con el número de cifras binarias de short). A estas conversiones explícitas de tipo se les llama cast. El cast se hace poniendo el tipo al que se desea transformar entre paréntesis, como por ejemplo, long result; result = (long) (a/(b+c)); 12. Sincronización de threads (hilos). La sincronización nace de la necesidad de evitar que dos o más threads (hilos) traten de acceder a los mismos recursos al mismo tiempo. Por ejemplo, si un thread tratara de escribir en un fichero, y otro thread tuviera al mismo tiempo tratando de borrar dicho fichero, se produciría una situación no deseada. Otra situación en la que hay que sincronizar threads se produce cuando un thread debe esperar a que estén preparados los datos que le debe suministrar el otro thread. 63 Capítulo 5 – Implementación de los conceptos de OO con JAVA Capítulo 5. IMPLEMENTACIÓN DE LOS CONCEPTOS DE OO CON JAVA En este capítulo se propone fijar los conceptos aprendidos, pero a través de programas de aplicación que utilicen cada uno de ellos. Implementado el concepto Jerarquía Los temas a implementar son: La Jerarquía de Clases La jerarquía de clases que describen las figuras planas Consideremos las figuras planas cerradas como el rectángulo y el círculo. Tales figuras comparten características comunes, tales como, la posición de la figura, de su centro y el área de la figura, aunque el procedimiento para calcular el área sea completamente distinto. Podemos entonces, diseñar una jerarquía de clases, tal que la clase base denominada Figura, tenga las características comunes y cada clase derivada las específicas. La relación jerárquica se muestra en la figura siguiente: Figura Circulo Rectángulo La clase Figura es la que contiene las características comunes a dichas figuras concretas por lo tanto, no tiene forma ni tiene área. Esto lo expresamos declarando Figura como una clase abstracta, declarando la función miembro area abstract. Las clases abstractas solamente se pueden usar como clases base para otras clases. No se pueden crear objetos pertenecientes a una clase abstracta. Sin embargo, se pueden declarar variables de dichas clases. En el juego del ajedrez podemos definir una clase base denominada Pieza, con las características comunes a todas las piezas, como es su posición en el tablero, y derivar de ella las características específicas de cada pieza particular. Así pues, la clase Pieza será una clase abstracta con una función abstract denominada mover, y cada tipo de pieza definirá dicha función de acuerdo a las reglas de su movimiento sobre el tablero. 65 Capítulo 5 – Implementación de los conceptos de OO con JAVA La clase Figura La definición de la clase abstracta Figura, contiene la posición x e y de la figura particular, de su centro, y la función area, que se va a definir en las clases derivadas para calcular el área de cada figura en particular. public abstract class Figura { protected int x; protected int y; public Figura(int x, int y) { this.x=x; this.y=y; } public abstract double area(); } La clase Rectángulo Las clases derivadas heredan los miembros dato x e y de la clase base, y definen la función area, declarada abstract en la clase base Figura, ya que cada figura particular tiene una fórmula distinta para calcular su área. Por ejemplo, la clase derivada Rectangulo, tiene como datos, aparte de su posición (x, y) en el plano, sus dimensiones, es decir, su ancho y alto. class Rectangulo extends Figura { protected double ancho, alto; public Rectangulo(int x, int y, double ancho, double alto){ super(x,y); this.ancho=ancho; this.alto=alto; } public double area(){ return ancho*alto; } } La primera sentencia en el constructor de la clase derivada es una llamada al constructor de la clase base, para ello se emplea la palabra reservada super. El constructor de la clase derivada llama al constructor de la clase base y le pasa las coordenadas del punto x e y. Después inicializa sus miembros dato ancho y alto. En la definición de la función area, se calcula el área del rectángulo como producto de la anchura por la altura, y se devuelve el resultado 66 Capítulo 5 – Implementación de los conceptos de OO con JAVA La clase Círculo class Circulo extends Figura { protected double radio; public Circulo(int x, int y, double radio){ super(x,y); this.radio=radio; } public double area(){ return Math.PI*radio*radio; } } Como vemos, la primera sentencia en el constructor de la clase derivada es una llamada al constructor de la clase base empleando la palabra reservada super. Posteriormente, se inicializa el miembro dato radio, de la clase derivada Círculo. En la definición de la función area, se calcula el área del círculo mediante la conocida fórmula p*r2, o bien p*r*r. La constante Math.PI es una aproximación decimal del número irracional pi. Uso de la jerarquía de clases. Creamos un objeto c de la clase Círculo situado en el punto (0, 0) y de 5.5 unidades de radio. Calculamos y mostramos el valor de su área. Circulo c=new Circulo(0, 0, 5.5); System.out.println("Area del círculo "+c.area()); Creamos un objeto r de la clase Rectangulo situado en el punto (0, 0) y de dimensiones 5.5 de anchura y 2 unidades de largo. Calculamos y mostramos el valor de su área. Rectangulo r=new Rectangulo(0, 0, 5.5, 2.0); System.out.println("Area del rectángulo "+r.area()); Veamos ahora, una forma alternativa, guardamos el valor devuelto por new al crear objetos de las clases derivadas en una variable f del tipo Figura (clase base). 67 Capítulo 5 – Implementación de los conceptos de OO con JAVA Figura f=new Circulo(0, 0, 5.5); System.out.println("Area del círculo "+f.area()); f=new Rectangulo(0, 0, 5.5, 2.0); System.out.println("Area del rectángulo "+f.area()); Enlace dinámico o binding. En el lenguaje C, los identificadores de la función están asociados siempre a direcciones físicas antes de la ejecución del programa, esto se conoce como enlace temprano o estático. Ahora bien, los lenguajes C++ y Java permiten decidir a que función llamar en tiempo de ejecución, esto se conoce como enlace tardío o dinámico. Podemos crear un array de la clase base Figura y guardar en sus elementos los valores devueltos por new al crear objetos de las clases derivadas. Figura[] fig=new Figura[4]; fig[0]=new Rectangulo(0,0, 5.0, 7.0); fig[1]=new Circulo(0,0, 5.0); fig[2]=new Circulo(0, 0, 7.0); fig[3]=new Rectangulo(0,0, 4.0, 6.0); ¿A qué función area llamará la sentencia: ? fig[i].area(); La respuesta será, según sea el índice i. Si i es cero, el primer elemento del array guarda una referencia a un objeto de la clase Rectangulo, luego llamará a la función miembro area de Rectangulo. Si i es uno, el segundo elemento del array guarda una referencia un objeto de la clase Circulo, luego llamará también a la función area de Circulo, y así sucesivamente. Podemos introducir el valor del índice i, a través del teclado, o seleccionando un control en un applet, en el momento en que se ejecuta el programa. Luego, la decisión sobre qué función area se va a llamar se retrasa hasta el tiempo de ejecución. Ejemplo de polimorfismo. Supongamos que deseamos saber la figura que tiene mayor área independientemente de su forma. Primero, programamos una función que halle el mayor de varios números reales positivos. 68 Capítulo 5 – Implementación de los conceptos de OO con JAVA double valorMayor(double[] x){ double mayor=0.0; for (int i=0; i<x.length; i++) if(x[i]>mayor){ mayor=x[i]; } return mayor; } Ahora, la llamada a la función valorMayor double numeros[]={3.2, 3.0, 5.4, 1.2}; System.out.println("El valor mayor es " + valorMayor(numeros)); La función figuraMayor que compara el área de figuras planas es semejante a la función valorMayor anteriormente definida, se le pasa el array de objetos de la clase base Figura. La función devuelve una referencia al objeto cuya área es la mayor. static Figura figuraMayor(Figura[] figuras){ Figura mFigura=null; double areaMayor=0.0; for(int i=0; i<figuras.length; i++){ if(figuras[i].area()>areaMayor){ areaMayor=figuras[i].area(); mFigura=figuras[i]; } } return mFigura; } La clave de la definición de la función está en las líneas if(figuras[i].area()>areaMayor){ areaMayor=figuras[i].area(); mFigura=figuras[i]; } En la primera línea, se llama a la versión correcta de la función area dependiendo de la referencia al tipo de objeto que guarda el elemento figuras[i] del array. En areaMayor se guarda el valor mayor de las áreas calculadas, y en mFigura, la figura cuya área es la mayor. La principal ventaja de la definición de esta función estriba en que la función figuraMayor está definida en términos de la variable figuras de la clase base 69 Capítulo 5 – Implementación de los conceptos de OO con JAVA Figura, por tanto, trabaja no solamente para una colección de círculos y rectángulos, sino también para cualquier otra figura derivada de la clase base Figura. Así si se deriva Triangulo de Figura, y se añade a la jerarquía de clases, la función figuraMayor podrá manejar objetos de dicha clase, sin modificar para nada el código de la misma. Veamos ahora la llamada a la función figuraMayor Figura[] fig=new Figura[4]; fig[0]=new Rectangulo(0,0, 5.0, 7.0); fig[1]=new Circulo(0,0, 5.0); fig[2]=new Circulo(0, 0, 7.0); fig[3]=new Rectangulo(0,0, 4.0, 6.0); Figura fMayor=figuraMayor(fig); System.out.println(“El área mayor es “+fMayor.area()); Pasamos el array fig a la función figuraMayor, el valor que retorna lo guardamos en fMayor. Para conocer el valor del área, desde fMayor se llamará a la función miembro area. Se llamará a la versión correcta dependiendo de la referencia al tipo de objeto que guarde por fMayor. Si fMayor guarda una referencia a un objeto de la clase Circulo, llamará a la función area definida en dicha clase. Si fMayor guarda una referencia a un objeto de la clase Rectangulo, llamará a la función area definida en dicha clase, y así sucesivamente. La combinación de herencia y enlace dinámico se denomina polimorfismo. El polimorfismo es, por lo tanto, la técnica que permite pasar un objeto de una clase derivada a funciones que conocen el objeto solamente por su clase base. Ampliación de la jerarquía de clases. Ampliamos el árbol jerárquico de las clases que describen las figuras planas regulares: Figura Triángulo Rectángulo Círculo Cuadrado 70 Capítulo 5 – Implementación de los conceptos de OO con JAVA La clase Cuadrado es una clase especializada de Rectangulo, ya que un cuadrado tiene los lados iguales. El constructor solamente precisa de tres argumentos los cuales corresponden a la posición de la figura y a la longitud del lado. class Cuadrado extends Rectangulo{ public Cuadrado(int x, int y, double dimension){ super(x, y, dimension, dimension); } } El constructor de la clase derivada llama al constructor de la clase base y le pasa la posición x e y de la figura, el ancho y alto que tienen el mismo valor. No es necesario redefinir una nueva función area. La clase Cuadrado hereda la función area definida en la clase Rectangulo. La clase Triangulo. Esta clase tiene como datos, aparte de su posición (x, y) en el plano, la base y la altura del triángulo. class Triangulo extends Figura{ protected double base, altura; public Triangulo(int x, int y, double base, double altura){ super(x, y); this.base=base; this.altura=altura; } public double area(){ return base*altura/2; } } El constructor de la clase Triangulo llama al constructor de la clase Figura, le pasa las coordenadas x e y de su centro, y luego inicializa los miembros dato base y altura. En la definición de la función area, se calcula el área del triángulo como producto de la base por la altura y dividido por dos. 71 Capítulo 5 – Implementación de los conceptos de OO con JAVA El operador instanceof. El operador instanceof tiene dos operandos: un objeto en el lado izquierdo, y, una clase en el lado derecho. Esta expresión devuelve true o false dependiendo de que el objeto situado a la izquierda sea o no una instancia de la clase situada a la derecha o de alguna de sus clases derivadas. Rectangulo rect=new Rectangulo(0, 0, 5.0, 2.0); rect instanceof String //false rect instanceof Rectangulo //true El objeto rect de la clase Rectangulo no es un objeto de la clase String. El objeto rect si es un objeto de la clase Rectangulo. Veamos la relación entre rect y las clases de la jerarquía rect instanceof Figura rect instanceof Cuadrado //true //false rect es un objeto de la clase base Figura pero no es un objeto de la clase derivada Cuadrado En resumen: La herencia es la propiedad que permite la creación de nuevas clases a partir de clases ya existentes. La clase derivada hereda los datos y las funciones miembro de la clase base, y puede redefinir algunas de las funciones miembro o definir otras nuevas, para ampliar la funcionalidad que ha recibido de la clase base. Para crear un objeto de la clase derivada se llama primero al constructor de la clase base mediante la palabra reservada super. Luego, se inicializan los miembros dato de dicha clase derivada. El polimorfismo se implementa por medio de las funciones abstractas, en las clases derivadas se declara y se define una función que tiene el mismo nombre, el mismo número de parámetros y del mismo tipo que en la clase base, pero que da lugar a un comportamiento distinto, específico de los objetos de la clase derivada. (redefinición de métodos) 72 Capítulo 5 – Implementación de los conceptos de OO con JAVA Enlace dinámico significa que la decisión sobre la función a llamar se demora hasta el tiempo de ejecución del programa. No se pueden crear objetos de una clase abstracta pero sí se pueden declarar referencias en las que guardamos el valor devuelto por new al crear objetos de las clases derivadas. Esta peculiaridad nos permite pasar un objeto de una clase derivada a una función que conoce el objeto solamente por su clase base. De este modo podemos ampliar la jerarquía de clases sin modificar el código de las funciones que manipulan los objetos de las clases de la jerarquía. Clases y Métodos Finales Clases Finales. Se puede declarar una clase como final, cuando no nos interesa crear clases derivadas de dicha clase. La clase Cuadrado se puede declarar como final, ya que no se espera que ningún programador necesite crear clases derivadas de Cuadrado. final class Cuadrado extends Rectangulo{ public Cuadrado(int x, int y, double dimension){ super(x, y, dimension, dimension); } } Uno de los mecanismos que tienen los hackers (piratas) para dañar o para obtener información privada en los sistemas es la de crear una clase derivada y sustituir dicha clase por la original. La clase derivada actúa exactamente igual que la original pero también puede hacer otras cosas, normalmente, dañinas. Para prevenir los posibles daños, se declara la clase como final, impidiendo a cualquier programador la creación de clases derivadas de ésta. Por ejemplo, la clase String que es una de las más importantes en la programación en lenguaje Java, está declarada como final. El lenguaje Java garantiza que siempre que se utilice un string, es un objeto de la clase String que se encuentra en el paquete java.lang.String, y no cualquier otro string. Métodos finales. Como sabemos, una de las formas de aprovechar el código existente, es la de crear una clase derivada y redefinir algunos de los métodos de la clase base. 73 Capítulo 5 – Implementación de los conceptos de OO con JAVA class Base{ //... final public void funcionFinal(){ //... } public void dibujar(Graphics g){ } } class Derivada{ //... public void dibujar(Graphics g){ // dibujar algunas figuras } } La clase Base define una función miembro pública “dibujar”, que no dibuja nada en el contexto gráfico g. La clase Derivada redefine la función miembro dibujar, para dibujar algunas figuras en el contexto grafico g. La función que se redefine tiene que tener la misma declaración en la clase Base y en la clase Derivada. Para evitar que las clases derivadas redefinan una función miembro de una clase base, se le antepone la palabra clave final. La función miembro funcionFinal de la clase Base no se puede redefinir en la clase Derivada, pero si se puede redefinir la función miembro dibujar. Interfases. Una interfase es una colección de declaraciones de métodos (sin definirlos) y también puede incluir constantes. Runnable es un ejemplo de interfase en el cual se declara, pero no se implementa, una función miembro run. public interface Runnable { public abstract void run(); } Las clases que implementen (implements) el interfase Runnable han de definir obligatoriamente la función run. 74 Capítulo 5 – Implementación de los conceptos de OO con JAVA class Animacion implements Runnable{ //.. public void run(){ // define la función run } } El papel de la interfase es el de describir algunas de las características de una clase. Por ejemplo, el hecho de que una persona sea un futbolista no define su personalidad completa, pero hace que tenga ciertas características que las distinguen de otras. Clases que no están relacionadas pueden implementar la interfase Runnable, por ejemplo, una clase que describa una animación, y una clase que realice un cálculo intensivo. Diferencias entre una Interfase y una Clase Abstracta. Una interfase es: una lista de métodos no implementados, puede incluir la declaración de constantes. Una clase abstracta puede incluir: métodos implementados y no implementados o abstractos, miembros dato constantes y otros no constantes. Ahora bien, la diferencia es mucho más profunda. Imaginemos que Runnable fuese una clase abstracta. Un applet definido por la clase MiApplet que moviese una figura por su área de trabajo, derivaría a la vez de la clase base Applet (que describe la funcionalidad mínima de un applet que se ejecuta en un navegador) y de la clase Runnable. En el lenguaje Java la clase MiApplet deriva de la clase base Applet e implementa el interfase Runnable class MiApplet extends Applet implements Runnable{ //... // define la función run de la interfase public void run(){ //... } // redefine paint de la clase base Applet public void paint(Graphics g){ //... } // define otras funciones miembro } 75 Capítulo 5 – Implementación de los conceptos de OO con JAVA Una clase solamente puede derivar extends de una clase base, pero puede implementar varias interfases. Los nombres de las interfases se colocan separados por una coma después de la palabra reservada implements. El lenguaje Java no fuerza una relación jerárquica, simplemente permite que clases no relacionadas puedan tener algunas características de su comportamiento similares. Las interfases y el Polimorfismo. En el lenguaje C++, es posible la herencia múltiple, pero este tipo de herencia presenta dificultades. Por ejemplo, cuando dos clases B y C derivan de una clase base A, y a su vez una clase D deriva de B y C. Este problema es conocido con el nombre de diamante. A C B C D En el lenguaje Java solamente existe la herencia simple, pero las clases pueden implementar interfases. Vamos a que la importancia de las interfases no consiste en resolver los problemas inherentes a la herencia múltiple sin forzar relaciones jerárquicas, sino es el de incrementar el polimorfismo del lenguaje más allá del que proporciona la herencia simple. Para explicar este aspecto importante y novedoso del lenguaje Java adaptaremos los ejemplos que aparecen en el artículo del Bill Venners "Designing with interfaces" publicado en Java World (http://www.javaworld.com/) en Diciembre de 1998. Comparemos la herencia simple mediante un ejemplo similar al de la jerarquía de las figuras planas, con las interfases. Herencia simple. Ver los programas completos: Animal.java, PoliApp.java Creamos una clase abstracta denominada Animal de la cual deriva las clases Gato y Perro. Ambas clases redefinen la función habla declarada abstracta en la clase base Animal. 76 Capítulo 5 – Implementación de los conceptos de OO con JAVA public abstract class Animal { public abstract void habla(); } class Perro extends Animal{ public void habla(){ System.out.println("¡Guau!"); } } class Gato extends Animal{ public void habla(){ System.out.println("¡Miau!"); } } El polimorfismo nos permite pasar la referencia a un objeto de la clase Gato a una función onomatopeya que conoce al objeto por su clase base Animal public class PoliApp { public static void main(String[] args) { Gato gato=new Gato(); onomatopeya(gato); } static void onomatopeya(Animal sujeto){ sujeto.habla(); } } El compilador no sabe exactamente que objeto se le pasará a la función onomatopeya en el momento de la ejecución del programa. Si se pasa un objeto de la clase Gato se imprimirá ¡Miau!, si se pasa un objeto de la clase Perro se imprimirá ¡Guau!. El compilador solamente sabe que se le pasará un objeto de alguna clase derivada de Animal. Por tanto, el compilador no sabe que función habla será llamada en el momento de la ejecución del programa. El polimorfismo nos ayuda a hacer el programa más flexible, porque en el futuro podemos añadir nuevas clases derivadas de Animal, sin que cambie para nada el método onomatopeya. Ver programas completos donde se trata el tema interfases: Parlanchin.java, Animal.java, Reloj.java, PoliApp.java 77 Capítulo 5 – Implementación de los conceptos de OO con JAVA Vamos a crear una interfase denominada Parlanchin que contenga la declaración de una función denominada habla. public interface Parlanchin { public abstract void habla(); } Hacemos que la jerarquía de clases que deriva de Animal implemente la interfase Parlanchin public abstract class Animal implements Parlanchin{ public abstract void habla(); } class Perro extends Animal{ public void habla(){ System.out.println("¡Guau!"); } } class Gato extends Animal{ public void habla(){ System.out.println("¡Miau!"); } } Ahora veamos otra jerarquía de clases completamente distinta, la que deriva de la clase base Reloj. Una de las clases de dicha jerarquía Cucu implementa la interfase Parlanchin y por lo tanto, debe de definir obligatoriamente la función habla declarada en dicha interfase. public abstract class Reloj { } class Cucu extends Reloj implements Parlanchin{ public void habla(){ System.out.println("¡Cucu, cucu, ..!"); } } Definamos la función onomatopeya de modo que conozca al objeto que se le pasa, no por una clase base, sino por la interfase Parlanchin. A dicha función le podemos pasar cualquier objeto que implemente la interfase Parlanchin, esté o no en la misma jerarquía de clases. 78 Capítulo 5 – Implementación de los conceptos de OO con JAVA public class PoliApp { public static void main(String[] args) { Gato gato=new Gato(); onomatopeya(gato); Cucu cucu=new Cucu(); onomatopeya(cucu); } static void onomatopeya(Parlanchin sujeto){ sujeto.habla(); } } Al ejecutar el programa, veremos que se imprime en la consola ¡Miau!, porque a la función onomatopeya se le pasa un objeto de la clase Gato, y después ¡Cucu, cucu, ..!, porque a la función onomatopeya se le pasa un objeto de la clase Cucu. Si solamente hubiese herencia simple, Cucu tendría que derivar de la clase Animal (lo que no es lógico) o bien no se podría pasar a la función onomatopeya. Con interfases, cualquier clase en cualquier familia puede implementar la interfase Parlanchin, y se podrá pasar un objeto de dicha clase a la función onomatopeya. Esta es la razón por la cual las interfases proporcionan más polimorfismo que el que se puede obtener de una simple jerarquía de clases. Composición. Hay dos formas de reutilizar el código: mediante la herencia, mediante la composición: se utiliza una clase ya creada incluyendo instancias de la misma en nuevas clases representa una relación "tiene un". Es decir, si tenemos una clase Rueda y una clase Coche, es de esperar que la clase Coche tenga cuatro instancias de Rueda: class Coche { Rueda rueda1, rueda2, rueda3, rueda 4; ... } La composición significa utilizar objetos dentro de otros objetos. Por ejemplo, un applet es un objeto que contiene en su interior otros objetos como botones, etiquetas, etc. Cada uno de los controles está descrito por una clase. 79 Capítulo 5 – Implementación de los conceptos de OO con JAVA Vamos a estudiar una clase Rectangulo definiendo el origen, no como un par de coordenadas x e y (números enteros) sino como objetos de una clase denominada Punto. Ver Programas completos: Punto.java, Rectángulo.java, RectanguloApp.java La clase punto La clase Punto tiene dos miembros dato, la abscisa x y la ordenada y de un punto del plano. Definimos dos constructores uno por defecto que sitúa el punto en el origen, y otro constructor explícito que proporciona las coordenadas x e y de un punto concreto. public class Punto { int x; int y; // funciones miembro } El constructor explícito de la clase Punto podemos escribirlo de dos formas public Punto(int x1, int y1) { x = x1; y = y1; } Cuando el nombre de los parámetros es el mismo que el nombre de los miembros datos escribimos public Punto(int x, int y) { this.x = x; this.y = y; } this.x que está a la izquierda y que recibe el dato x que se le pasa al constructor se refiere al miembro dato, mientras que x que está a la derecha es el parámetro. Recordemos que this es una palabra reservada que guarda una referencia al objeto propio, u objeto actual. La función miembro desplazar simplemente cambia la posición del punto desde (x, y) a (x+dx, y+dy). Cuando es llamada, recibe en sus dos parámetros dx y dy el desplazamiento del punto y actualiza las coordenadas x e y del punto. La función no retorna ningún valor. 80 Capítulo 5 – Implementación de los conceptos de OO con JAVA public void desplazar(int dx, int dy){ x += dx; y += dy; } Para crear un objeto de la clase Punto cuyas coordenadas x e y valgan respectivamente 10 y 23 escribimos Punto p = new Punto(10, 23); Para desplazar el punto p 10 unidades hacia la izquierda y 40 hacia abajo, llamamos desde el objeto p a la función desplazar y le pasamos el desplazamiento horizontal y vertical. p.desplazar(-10, 40); La clase Rectangulo La clase Rectangulo tiene como miembros dato, el origen, que es un objeto de la clase Punto y las dimensiones ancho y alto. public class Rectangulo { Punto origen; int ancho; int alto ; // funciones miembro } El constructor por defecto, crea un rectángulo situado en el punto 0,0 y con dimensiones nulas public Rectangulo() { origen = new Punto(0, 0); ancho=0; alto=0; } El constructor explícito crea un rectángulo situado en un determinado punto p y con unas dimensiones que se le pasan en el constructor 81 Capítulo 5 – Implementación de los conceptos de OO con JAVA public Rectangulo(Punto p, int w, int h) { origen = p; ancho = w; alto = h; } Podemos definir otros constructores en términos del constructor explícito usando la palabra reservada this. public Rectangulo(Punto p) { this(p, 0, 0); } public Rectangulo(int w, int h) { this(new Punto(0, 0), w, h); } El primero crea un rectángulo de dimensiones nulas situado en el punto p. El segundo, crea un rectángulo de unas determinadas dimensiones, w y h, situándolo en el punto 0, 0. Dentro del cuerpo de cada constructor, se llama al constructor explícito mediante this pasándole en sus parámetros los valores apropiados. Para desplazar un rectángulo, trasladamos su origen (esquina superior izquierda) a otra posición, sin cambiar su anchura o altura. Desde el objeto origen, llamamos a la función desplazar miembro de la clase Punto void desplazar(int dx, int dy) { origen.desplazar(dx, dy); } Objetos de la clase Rectángulo. Para crear un rectángulo rect1 situado en el punto (0, 0) y cuyas dimensiones son 100 y 200 escribimos Rectangulo rect1=new Rectangulo(100, 200); Para crear un rectángulo rect2, situado en el punto de coordenadas 44, 70 y de dimensiones nulas escribimos: Punto p=new Punto(44, 70); Rectangulo rect2=new Rectangulo(p); 82 Capítulo 5 – Implementación de los conceptos de OO con JAVA O bien, en una sóla línea Rectangulo rect2=new Rectangulo(new Punto(44, 70)); Para desplazar el rectángulo rect1 desde el punto (100, 200) a otro punto situado 40 unidades hacia la derecha y 20 hacia abajo, sin modificar sus dimensiones, escribimos: rect1.desplazar(40, 20); Para hallar y mostrar el área del rectángulo rect1 podemos escribir System.out.println("el área es " + rect1.calcularArea()); Para hallar el área de un rectángulo de 100 unidades de largo y 50 de alto y guardar el resultado en la variable entera areaRect, escribimos en una sola línea: int areaRect=new Rectangulo(100, 50).calcularArea(); Definiendo Clases. Definición de Clases. Cuando trabajamos con el flujo de entrada y salida, quizás vimos que hay pasos muy tediosos. Quizás no se comportan los objetos de estas clases tal y como nosotros queremos. ¿Tenemos que aceptar un comportamiento de un objeto inadecuado para nuestros propósitos solo porque otro programador los diseñó así? No, nosotros podemos definir nuestras propias clases que provean el comportamiento que nosotros requerimos. Definición de Clase Simple: class Saludos { public Saludos(){ } public void saludo(){ System.out.println("Hola mundo!"); } } Esto define una clase Saludos con un constructor y un solo método saludo que despliega el mensaje "Hola mundo!". Usando esta definición podemos crear un objeto de la siguiente forma: Saludos x; x = new Saludos(); 83 Capítulo 5 – Implementación de los conceptos de OO con JAVA Podemos mandarle un mensaje de la siguiente forma: x.saludo(); En general todos los métodos llevan la palabra reservada public al principio. Un método simple tiene la estructura: public valor-retorno nombre-metodo (){ // cuerpo del método } La palabra reservada void es usada con aquellos métodos que no devolverán nada. Definición de Métodos con Parámetros. Supongamos que queremos diseñar un método saludo mas genérico, en donde al mandar el mensaje podamos escoger a quien saludar. Esta información adicional, la tendría que incluir el que llama al método como argumento de la siguiente forma: x.saludo("alumnos") Para desplegar "Hola alumnos!". Para poder implementar esto necesitamos incluir parámetros en el encabezado del método, para que este tenga acceso a esta información. La nueva clase quedaría entonces así: class Saludos { public Saludos(){ } public void saludo(){ System.out.println("Hola mundo!"); } public void saludo(String alguien){ System.out.println("Hola".concat(alguien).concat("!")); } } Al incluir dos métodos con el mismo nombre, pero diferente firma, public void saludo() public void saludo(String alguien) estamos sobrecargando el método saludo. Por lo tanto cuando el usuario quiere saludar a todo el mundo, hace la llamada: 84 Capítulo 5 – Implementación de los conceptos de OO con JAVA x.saludo(); pero cuando quiere saludar a alguien en especial, hace la llamada x.saludo("Colegio"); Como podemos saludar a todo el mundo sin especificarlo explícitamente, se dice que el saludo al mundo es el saludo default. Uso de Atributos. Supongamos que queramos darle la opción al creador del objeto Saludos que escoja el saludo default, para que no necesariamente sea al "mundo". Esta información debería de ser proveída en el constructor del objeto. Así, con las siguientes instrucciones: desplegarían un saludo a la universidad: Saludos x; x = new Saludos("Universidad"); x.saludos(); Para poder implementar esto, debemos incluir un constructor que reciba una cadena de argumento. Es decir, que se debe incluir un constructor con un parámetro String. Para que tenga el método saludo acceso a la cadena recibida en el constructor, necesitamos almacenarla en un atributo. Esto lo hacemos porque un método no puede acceder a las variables o parámetros de los otros métodos. Los atributos pueden ser accedidos por todos los métodos de dentro de la clase. La declaración de estos es la misma que las variables, con la diferencia que comienzan con la palabra reservada private. La nueva clase quedaría así: class Saludos { private String quien; public Saludos(){ quien = "mundo"; } public Saludos(String s){ quien = s; } } 85 public void saludo(){ System.out.println("Hola ".concat(quien).concat("!")); } public void saludo(String alguien){ System.out.println("Hola ".concat(alguien).concat("!")); } Capítulo 5 – Implementación de los conceptos de OO con JAVA El orden de declaración entre los atributos y los métodos es irrelevante. Cualquier orden es legal. Como norma, conviene para la clase, que declararemos todos los atributos antes de los métodos. Los miembros (atributos y métodos de la clase) pueden ser públicos, privados o amigables (default). Los miembros públicos (public) pueden ser accedidos por todos. Los miembros amigables (friendly) pueden ser accedidos por miembros del mismo paquete (conjunto de clases). Los miembros privados (private) solo pueden ser accedidos por miembros de la misma clase. Uso de la Definición de Clase. El primer paso que tenemos que hacer es escribir nuestra clase Saludos en un archivo que tiene que llamarse Saludos.java. Como esta clase utiliza el PrintStream, asegúrese de incluir la línea: import java.io.*; Después compilamos este archivo para producir Saludos.class. Una vez que ya hicimos esto, podemos utilizar nuestra clase en un programa. Por ejemplo, podríamos tener: import java.io.*; class SaludandoUnPoco { public static void main(String args[]) { System.out.println("Saludando un poco a todos!"); Saludos s1, s2; s1 = new Saludos(); s2 = new Saludos("FISICC"); } } s1.saludo(); s2.saludo(); s1.saludo("Suger Montano"); Ya el programa SaludandoUnPoco se compila y se corre de la manera usual. Diseño e Implementación de Clases. La definición de la clase trabajada anteriormente fue extremadamente casual. Empezamos con una clase muy simple y le fuimos agregando funcionalidad mientras 86 Capítulo 5 – Implementación de los conceptos de OO con JAVA lo íbamos necesitando. Dicho enfoque fue útil para explicar la definición de las clases, pero es poco funcional para escribir clases de alguna significación. Es como construir una casa, cuarto por cuarto. Necesitamos un enfoque más sistemático. Podemos seguir los siguientes pasos: Diseño de la clase: Decida el comportamiento que la clase deberá proveer. Como el comportamiento de un objeto es proveído a través de sus métodos, determine claramente que métodos debe proveer la clase. Determine la interfase de la clase que se usará. La interfase de la clase es la forma en que podemos usar un objeto de esta clase. En este paso ya es necesario determinar los prototipos de los métodos. Escriba un programa de prueba que utilice la clase. Esta es una forma de revisar si lo que tenemos hasta ahora tiene sentido. Escriba un esqueleto de la clase. Este, el esqueleto, es la declaración de la clase con los prototipos de todos los métodos, pero con el cuerpo de los métodos vacío. 87 Capítulo 5 – Implementación de los conceptos de OO con JAVA Implementación de la clase: Consiste en escribir los cuerpos de los métodos y declarar los atributos mientras se vayan necesitando. Tenga en mente lo siguiente: Puede empezar a trabajar en cualquiera de los métodos. Puede dejar de trabajar en cualquiera de los métodos antes de completarlo e ir a trabajar en otro método. Mejora de la Implementación Por lo general cualquier código escrito, a pesar de que funcione puede ser mejorado. La interfase se debe mantener intacta. Primer Ejemplo Completo: Clase para Entrada/Salida Interactiva (InteractiveIO) Para ilustrar este enfoque de diseño e implementación de clases, vamos a comenzar resolviendo el problema de entrada y salida interactiva. Queremos diseñar una clase que provea una manera sencilla de leer y desplegar información. Diseño de la Clase. Comportamiento que la clase deberá proveer. Si tuviéramos una clase InteractiveIO quisiéramos que esta me ofreciera el siguiente comportamiento: Escribir a la pantalla, asegurándome que se vaya a desplegar inmediatamente. Preguntar al usuario por información, desplegándole un mensaje y leyendo una cadena del teclado, asegurándome que el mensaje se despliegue antes de que la computadora espere la entrada. Interfase y Prototipos de los métodos. A partir del comportamiento deseado de InteractiveIO, quisiéramos tener la capacidad de hacer lo siguiente: Declarar una referencia a la clase InteractiveIO de la siguiente forma: InteractiveIO interIO; Esto me dice simplemente que la clase se debe llamar InteractiveIO. Crear objetos que sean instancias de InteractiveIO, sin hacer referencia a System.in o System.out de la siguiente forma: interIO = new InteractiveIO(); 88 Capítulo 5 – Implementación de los conceptos de OO con JAVA De esto podemos deducir que necesitaremos un constructor sin parámetros. Mandar un mensaje al objeto para escribir a pantalla de la siguiente forma: interIO.write("Por favor conteste cada pregunta."); Debemos proveer un método write que reciba una cadena como parámetro, sin devolver nada. Mandar un mensaje al objeto para preguntar una cadena, desplegando una frase descriptiva de lo que queremos leer, de la siguiente forma: String s; s = interIO.promptAndRead("Cual es su nombre?"); Debemos proveer un método promptAndRead que reciba una cadena como parámetro, y que devuelva una cadena. Aquí ya tenemos una idea clara del prototipo de los métodos que incluirá la clase InteractiveIO. Estos son: public InteractiveIO() public void write(String s) public String promptAndRead(String s) Programa de prueba que utilice la clase. Para usar la clase, el programa de prueba más simple que podemos escribir es uno que lee una palabra y a continuación la despliega. Si nuestra clase InteractiveIO funcionara como nosotros quisiéramos este quedaría así: import java.io.*; class ProbandoInteractiveIO { public static void main(String args[]) { InteractiveIO interIO; String line; interIO = new InteractiveIO(); line = interIO.promptAndRead("Ingrese una palabra: "); interIO.write(line); } } Concluimos que una clase InteractiveIO diseñada tal y como la tenemos, lleva a cabo bien la tarea. Provee de una entrada y salida interactiva simple, despreocupándonos de System.in, System.out y flush. Esta clase haría todo esto por nosotros. 89 Capítulo 5 – Implementación de los conceptos de OO con JAVA Esqueleto de la clase. class InteractiveIO { // aqui irían los atributos, si necesitáramos public InteractiveIO() { // enunciados } /** despliega s*/ public void write(String s) { // enunciados } } /** despliega s, lee una cadena del teclado y devuelve una referencia a esta */ public String promptAndRead(String s) { // enunciados } Implementación de la clase Completamos los métodos de la clase. class InteractiveIO { // aparentemente no necesitamos atributos public InteractiveIO() { // parece que necesitamos nada! } /** despliega s*/ public void write(String s) { System.out.print(s); System.out.flush(); } /** despliega s, lee una cadena del teclado y devuelve una referencia a esta */ } public String promptAndRead(String s) throws Exception { System.out.print(s); System.out.flush(); BufferedReader br = new BufferedRead( new InputSteamReader(System.in)); String line; line = br.readLine(); return line; } 90 Capítulo 5 – Implementación de los conceptos de OO con JAVA La palabra reservada return se utiliza para dos operaciones en Java: Para retornar un valor. Su sintaxis es: return expresion; Para forzar la salida de un método. No se ejecutara ninguna instrucción después del return. Incluso si tengo un método que no retorna ningún valor, pero deseo salirme del método lo puedo hacer mediante el enunciado: return; Mejora de la implementación. Observemos que podemos optimizar el programa en los siguientes aspectos: No tiene sentido crear en cada llamada al método write una instancia del BufferedReader. En vez de esto podemos crearlo una sola vez en el constructor y almacenar una referencia a este como atributo (para que sea accesible desde el método write). Las primeras instrucciones del método promptAndRead son iguales a las del método write. Podemos ahorrar esta repetición de código llamando al método write desde el método promptAndRead. No necesitamos almacenar la referencia al objeto String en el método promptAndRead. Podemos obviar este paso devolviendo la referencia leída en composición. La nueva implementación de la clase InteractiveIO entonces me queda así: class InteractiveIO { private BufferedReader br; public InteractiveIO() throws Exception { br = new BufferedReader( new InputStreamReader(System.in)); } /** despliega s*/ public void write(String s) { System.out.print(s); System.out.flush(); } } 91 /** despliega s, lee una cadena del teclado y devuelve una referencia a esta */ public String promptAndRead(String s) { this.write(s); return keyb.readLine(); } Capítulo 5 – Implementación de los conceptos de OO con JAVA Aquí trabajamos con tres tipos de variables: Parámetros: son creadas cuando el método al cual pertenecen es invocado. Su valor inicial corresponde a la información enviada como argumento. Son destruidas cuando termina el método. Solo pueden ser accedidas por el método al cual pertenecen. Variables Locales: al igual que los parámetros, éstas son creadas cuando el método se invoca y destruidas cuando termina el método y sólo pueden ser accedidas por el método al cual pertenecen. A diferencia de los parámetros éstas deben ser inicializadas dentro del método. Atributos: tienen el mismo tiempo de vida que el objeto al cual pertenecen. Son creadas cuando el objeto es creado y destruidas cuando el objeto es destruido. Pueden ser accedidas por todos los métodos de la misma clase. Se acostumbra a declararlas private, ya que no deberían ser accedidas por métodos de otras clases. Recordemos que cuando mandamos mensajes (al llamar a los métodos) necesitamos un receptor de dicho mensaje. En el método promptAndRead cuando invocamos al mensaje write el receptor de este mensaje debe ser el mismo objeto al cual promptAndRead pertenece. Usamos la palabra reservada this para hacer referencia al objeto al cual le pertenece el método. Podemos utilizar también la palabra reservada this para diferenciar entre variables locales y atributos que tengan el mismo nombre. Ejercicios propuestos para el alumno: Agregue un método writeln a la clase InteractiveIO que reciba una cadena de caracteres, que la despliegue y que garantice que el siguiente carácter a desplegarse aparecerá en una nueva línea. Agregue un segundo método writeln (sobrecargado) en la clase InteractiveIO que no reciba argumentos, pero garantice que el siguiente carácter a desplegarse aparecerá en una nueva línea. Segundo Ejemplo Completo: Clase Nombre. Diseñamos la clase InteractiveIO motivados por el deseo de un uso más conveniente del PrintStream y del BufferedReader. Por lo general nuestro punto de partida no son las clases existentes de Java sino problemas de la vida real que deseamos resolver modelándolos de alguna forma. Las clases predefinidas que Java nos provee ni siquiera se acercan a modelar el comportamiento de los elementos de nuestro mundo. El ejemplo que vamos a trabajar ahora es modelar el nombre de una persona. Podemos estar tentados a utilizar la clase String, pero esta no nos provee el comportamiento que nosotros quisiéramos de una clase Nombre 92 Capítulo 5 – Implementación de los conceptos de OO con JAVA Diseño de la Clase. Comportamiento que la clase deberá proveer. Si tuviera una clase Nombre quisiera que esta me ofreciera el comportamiento para: Obtener las iniciales como una cadena. Obtener el nombre como una cadena; en el orden apellido, primer nombre Obtener el nombre como una cadena; en el orden primer nombre, apellido Agregar o reemplazar un titulo. Interfase y Prototipos de los métodos. A partir del comportamiento deseado de la clase Nombre, quisiéramos estar en la capacidad de hacer lo siguiente: Declarar una referencia a la clase Nombre de la siguiente forma: Nombre alumno; Esto me dice simplemente que la clase se debe llamar Nombre. Crear objetos que sean instancias de Nombre, basados en el primer nombre y el apellido de la siguiente forma: alumno = new Nombre("Juan", "Perez"); De esto podemos deducir que necesitaremos un constructor con dos parámetros String. Mandar un mensaje al objeto para obtener las iniciales como una cadena, de la siguiente forma: String s; s = alumno.getInitials(); Debemos proveer un método getInitials que devuelva una cadena. Mandar un mensaje al objeto para obtener el nombre completo, en la forma apellido, primer nombre; como una cadena, de la siguiente forma: String s; s = alumno.getLastFirst(); Debemos proveer un método getLastFirst que devuelva una cadena. Mandar un mensaje al objeto para obtener el nombre completo, en la forma primer nombre apellido precedido de un título opcional, como una cadena, de la siguiente forma: 93 Capítulo 5 – Implementación de los conceptos de OO con JAVA String s; s = alumno.getFirstLast(); Debemos proveer un método getFirstLast que devuelva una cadena. Mandar un mensaje al objeto para agregar o reemplazar el título de la siguiente forma: alumno.setTitle("Ingeniero"); Debemos proveer un método setTitle que reciba como parámetro una cadena. Aquí ya tenemos una idea clara del prototipo de los métodos que incluirá la clase Nombre. Estos son: public public public public public Nombre(String pnom, String ap) String getInitials() String getLastFirst() String getFirstLast() void setTitle(String newTitle) Observe que el constructor no incluye al titulo. Esta fue nuestra opción (como diseñadores de la clase). Nuestro razonamiento fue que: no todos tienen titulo. En contraste con el nombre y apellido, el titulo no es permanente, por lo que permitimos que se pudiera agregar un titulo o modificarlo. Programa de prueba que utilice la clase. Para usar la clase, el programa de prueba más simple que podemos escribir es uno que lea el primer nombre y apellido y el título. Después el programa deberá desplegar: las iniciales el nombre completo en la forma apellido, primer nombre el nombre completo en la forma primer nombre apellido. Una vez que tenemos funcionando nuestra clase InteractiveIO la utilizaremos en el programa de prueba. Si nuestra clase Nombre funcionara como nosotros quisiéramos este quedaría así: 94 Capítulo 5 – Implementación de los conceptos de OO con JAVA import java.io.*; class ProbandoNombre { public static void main(String args[]) { Nombre n; String primer, apellido, titulo; InteractiveIO io; io = new primer = apellido titulo = } } InteractiveIO(); io.promptAndRead("Ingrese primer nombre: "); = io.promptAndRead("Ingrese apellido: "); io.promptAndRead("Ingrese apellido:"); n = new Nombre(primer, apellido); n.setTitle(titulo); io.writeln(n.getInitials()); io.writeln(n.getFirstLast()); io.writeln(n.getLastFirst()); Concluimos que la clase Nombre diseñada tal y como la tenemos, lleva a cabo bien la tarea. Provee una forma simple de manipular los nombres. Esqueleto de la clase. class Nombre { // aquí van los atributos si las necesitáramos } public Nombre(String pnom, String ap) // enunciados } public String getInitials() { // enunciados } public String getLastFirst() { // enunciados } public String getFirstLast() { // enunciados } public void setTitle(String newTitle) // enunciados } { { Implementación de la clase Completamos los métodos de la clase. Razonamos que: Los métodos getFirstLast y getLastFirst necesitan ambos acceso al primer nombre y al apellido. De la misma forma getFirstLast y setTitle necesitan 95 Capítulo 5 – Implementación de los conceptos de OO con JAVA ambos acceso al título. Por lo tanto, necesitamos hacer del primer nombre, apellido y titulo, atributos. Asumiremos que mientras no se especifique un titulo la persona no lo tiene. class Nombre { private String primerNombre, apellido, titulo; } public Nombre(String pnom, String ap) { primerNombre = pnom; apellido = ap; titulo = ""; } public String getInitials() { String inicNom, inicAp; inicNom = primerNombre.substring(0,1); inicAp = apellido.substring(0,1); return inicNom.concat(".").concat(apellido).concat("."); } public String getLastFirst() { return apellido.concat(", ").concat(primerNombre); } public String getFirstLast() { return titulo.concat(" ").concat(primerNombre).concat( " ").concat(apellido); } public void setTitle(String newTitle) { titulo = newTitle; } El objetivo del constructor es garantizar que el objeto comience con valores válidos en sus atributos. Queda la mejora de la clase a discreción del alumno. Para la clase InteractiveIO, es poco probable que se declare más de un objeto de esta clase. Sin embargo, para el caso de la clase Nombre, es muy probable que se declaren muchas instancias de esta clase. Cada una de estas compartirá los mismos métodos pero tendrán su propio juego de atributos: primerNombre, apellido y titulo. Esto tiene sentido ya que cada objeto instancia de Nombre tendrá su propio primer nombre, apellido y titulo. A pesar de que todas las instancias de la clase Nombre vayan a tener el mismo comportamiento (proveído por sus métodos), las llamadas devolverán resultados distintos. Esto es porque los atributos tendrán distintos valores. Los valores de sus atributos constituyen su estado. Puedo mandarle un mensaje getLastFirst a dos objetos distintos, y se comportaran igual y pero pueden devolver distinto resultado ya que sus estados son distintos. 96 Capítulo 5 – Implementación de los conceptos de OO con JAVA Ejercicios propuestos para el alumno: Escriba un programa que lea tres pares de primer nombre, apellido y a continuación despliegue sus iniciales. El programa deberá usar la clase Nombre. Agregue un método IlustrateName a la clase Nombre que despliegue el nombre a pantalla. Diseñe e implemente una clase NombreLargo, que tome en consideración (además del primer nombre y apellido como lo hizo la clase Nombre) el segundo nombre. Diseñe e implemente una clase Direccion. Diseñando la Salida para los Objetos En el diseño original de la clase Nombre, la clase no leía ni desplegaba objetos Nombre. La entrada y salida era la responsabilidad del usuario de la clase. Algunas veces es deseable que la clase se haga responsable de la entrada y la salida. Para ilustrar como se podría implementar esto, le agregaremos este comportamiento a la clase Nombre, comenzando con la salida. Podemos diseñar un método print para la clase Nombre que provea el comportamiento de producir como salida el nombre completo. Hay que considerar dos aspectos en este punto: ¿En que forma se debe producir el nombre completo? La salida, ¿hacia donde debería ir? La primera cuestión es algo de diseño, arbitrariamente podemos escoger producir el nombre completo en el orden titulo primer-nombre apellido. Podemos de una forma arbitraria escoger hacia donde debería ir la salida, ya sea al monitor o a algún archivo en especial. Sin embargo, si nos vamos a tomar la molestia de agregar un método extra a la clase, podríamos diseñarlo para que sea lo más general posible sin limitarlo a ninguna salida específica. Podríamos hacer responsabilidad del que invoca el método, hacia donde debería producirse la salida. Hasta ahora, hemos manejado la salida a través del print y println ha instancias del PrintStream. Como diseñadores de la clase Nombre, podríamos insistir que la salida se maneje a través del PrintStream. Podríamos pedirle al invocador del método print que incluya como argumento el objeto PrintStream, a donde desea que se mande la salida. El método quedaría así: void print (PrintStream destino) De esta manera, si el que llama al método print desea desplegar el nombre en la pantalla, en la llamada, incluiría como argumento System.out. Por otro lado si 97 Capítulo 5 – Implementación de los conceptos de OO con JAVA desease mandar el nombre a un archivo incluiría como argumento alguna instancia del FileOutputStream. Un ejemplo de uso podría ser: Nombre n; PrintStream p; n = new Nombre("Jodie","Foster"); n.setTitle("Licenciada"); p = new PrintStream (new FileOutputStream ("actrices.txt")); n.print(System.out); // despliega Licenciada Jodie Foster en la pantalla n.print(p); // escribe Licenciada Jodie Foster en el archivo La implementación quedaría así: void print (PrintStream destino){ destino.print(this.getLastFirst()); } Ejercicio propuesto para el alumno: Implemente una clase llamada SalidaConBitacora. El constructor recibe un solo argumento: una cadena que representa el nombre de un archivo. La clase debe proveer dos métodos: print y println, ambos, de los cuales deben recibir una cadena de argumento. Estos métodos deben mandar la cadena a desplegar a pantalla y a escribirla al archivo. Diseñando la Entrada para los Objetos Desearíamos tener la capacidad de mandarle un mensaje pidiendo que se cree un objeto Nombre desde cierta entrada. Nos topamos con un problema: los mensajes los mandamos a objetos y no tenemos un objeto a quien mandarle el mensaje. De hecho, el envío del mensaje debería de crear el objeto. En realidad no deseamos mandarle el mensaje a un objeto instancia de la clase Nombre, sino a la clase Nombre. Esto en Java se lleva a cabo a través de un tipo de método conocido como método estático. Estos métodos son definidos de la misma forma que los otros métodos, excepto que estos llevan la palabra reservada static antes del tipo de retorno. Los métodos estáticos no están asociados a ningún objeto. Tienen las siguientes características: Deben de ser invocados independientemente de cualquier instancia de la clase. Se utiliza el nombre de la clase como receptor del mensaje. No pueden accesar ningún atributo. Al igual que con el método print nos debemos preguntar dos cosas: o ¿En que forma se debe leer el nombre? o La entrada, ¿de donde debería provenir? 98 Capítulo 5 – Implementación de los conceptos de OO con JAVA Requeriremos como diseñadores de la clase, que el primer nombre aparezca en una línea de por sí solo y el apellido en la siguiente línea. De nuevo, esta decisión es arbitraria. Además diseñaremos el método de tal forma que la fuente de entrada sea responsabilidad del que invoque el método. Requeriremos que como argumento se pase una referencia a un BufferedReader, de donde será leído el nombre. El prototipo sería así: public static Nombre read(BufferedReader fuente) Podemos utilizar la clase Nombre para leer un nombre desde el teclado y desde un archivo de la siguiente forma: Nombre n; FileInputStream f; BufferedReader brFile, brKeyb; InputStreamReader isrFile, isrKeyb; isrKeyb = new InputStreamReader(System.in) brKeyb = new BufferedReader(isrKeyb); f = new FileInputStream(new File("nombres.txt")); isrFile = new InputStreamReader(f); brFile = new BufferedReader(isrFile); n = Nombre.read(brKeyb); n.print(System.out); n = Nombre.read(brFile); n.print(System.out); La implementación quedaría así: public static Nombre read(BufferedReader fuente){ String primer, apellido; primer = fuente.readLine(); apellido = fuente.readLine(); return new Nombre(primer, apellido); } Anteriormente vimos que, cuando se esta ingresando la información desde el teclado, es aconsejable desplegar un mensaje para decirle al usuario que información es la que se desea que ingrese. Podemos incluir otro método de entrada readi que utilice la clase InteractiveIO que ya hemos diseñado para llevar a cabo esto. La implementación quedaría así: public static Nombre readi(Interactive io){ String primer, apellido; primer = io.promptAndRead("Ingrese primer nombre:"); apellido = io.promptAndRead("Ingrese apellido:"); return new Nombre(primer, apellido); } 99 Capítulo 5 – Implementación de los conceptos de OO con JAVA Ejercicio propuesto para el alumno: Agregue los métodos read, readi y print a las clases que usted desarrolló anteriormente en ejercicios: NombreLargo y Direccion. Programa Hello world ! Revisado Con lo que hemos conocido hasta ahora, podemos explicar un poco más el primer programa que escribimos: import java.io.*; class HelloApp { public static void main (String args[]) { System.out.println ("Hello world !"); } } Vemos que el programa HelloApp es una clase. Toda clase que existe como programa necesita un método main. El método main es static, es decir no esta asociado a ningún objeto HelloApp. La razón de esto, es que la ejecución de nuestro programa debe de comenzar en algún lado. El intérprete de Java en algún sentido, invoca el método main. Pero esto lo hace antes de que cualquier objeto de nuestro código haya sido creado. Además vemos que esta declarado como public. La razón de esto es que necesita ser llamado desde afuera de la clase (desde el interprete de Java). 100 Capitulo 6 – Software de Calidad. Capítulo 6. CONTROL DE CALIDAD EN LOS SISTEMAS Introducción El desarrollo de sistemas hoy en día ha tomado gran importancia en el mundo, siendo ésta cada vez más creciente. Me parece muy acertado sugerir, en este punto, y luego de haber analizado en profundidad un lenguaje de programación, que si una empresa de software pretende competir a nivel internacional, es necesario que contemple seriamente la necesidad de realizar una CERTIFICACION DE CALIDAD DEL SOFTWARE Y DEL SISTEMA. En el entorno actual de la realidad económica de nuestro país, y considerando que se están gestando polos de DESARROLLO DE SOFTWARE, como el que se acaba de anunciar en la vecina ciudad de Resistencia, Provincia del Chaco, el que pretende generar fuentes de trabajo mas allá de las fronteras de la nación, el que ha tomado como modelo a la Republica de la India, debemos, no solo entonces elegir un buen lenguaje de programación, sino que también, el mismo debe estar acorde a las pautas mundiales de CALIDAD. A nivel país, el Gobierno de la Ciudad de Buenos Aires, esta impulsando de manera coherente desde el año 2003, el programa “PRO ISO 9000”. Este desafío impulsado por el Gobierno porteño, ya ha generado sus primeros frutos en el área informática, siendo la empresa MSA Magic Software Argentina SA, la primera en el rubro en Certificar sus procesos de desarrollo de software, el 21 de abril del 2004. Es claro que si un sistema presenta errores al momento de ser utilizado, ese producto pierde confiabilidad a los ojos del usuario hasta el nivel que podría ser desechado como un producto defectuoso. Muchos proyectos de sistemas presentan fallas que impiden que el sistema funcione como era de esperarse o que sea utilizado en su totalidad. Por ello, es necesario definir e impulsar líneas de acción tendientes a mejorar el sistema producido. Dentro de estas líneas de acción, está la relacionada con el proceso mismo del desarrollo del sistema, y como necesidad primordial, la de realizar una investigación que permita conocer de primera mano el estado en que se encuentra su proceso de desarrollo. Diferenciaremos dos conceptos importantes como son: Calidad en los Sistemas y Calidad en el Software. El control de calidad de sistemas está orientado a garantizar el funcionamiento de los sistemas en explotación. 101 Capitulo 6 – Software de Calidad. El control de calidad de software está orientado a garantizar las características de los programas que se están desarrollando. La Ingeniería de Software concierne a teorías, métodos y herramientas para el desarrollo profesional de productos. Un Sistema concierne a un conjunto de componentes interrelacionados trabajando conjuntamente para un fin común. El sistema puede incluir software, dispositivos mecánicos y eléctricos, hardware, y ser operado por gente. Existen varias organizaciones de estandarización internacional que realizan estándares y modelos de ingeniería de software, esto con el fin de mejorar la Calidad del Software. La Calidad La ISO (International Standard Organization), define La Calidad como la ausencia de deficiencias: "Es la totalidad de aspectos y características de un producto o servicio que se refieren a su capacidad para satisfacer necesidades dadas en la adecuación de sus objetivos'”. El Instituto de Ingeniería de Software (SEI) en su modelo CMM (Capability Maturity Model) define la calidad como: • El grado en el cual un sistema, componente o proceso cumple con los requisitos especificados. • El grado en el cual el sistema, componente o proceso cumple con las expectativas del cliente o usuario. La calidad consiste en todos aquellos aspectos del producto que satisfacen las necesidades del cliente y de ese modo proporcionan la satisfacción del producto. La calidad es sinónimo de eficiencia, flexibilidad, corrección, confiabilidad, mantenibilidad, portabilidad, usabilidad, seguridad e integridad. Calidad del Software Definición La Calidad del Software es el conjunto de cualidades que lo caracterizan y que determinan su utilidad y existencia. La Calidad del Software es mensurable y varía de un sistema a otro o de un programa a otro. Por ejemplo: un software elaborado para el control de naves espaciales debe ser confiable al nivel de "cero fallas"; un software hecho para ejecutarse una sola vez no requiere el mismo nivel de calidad; mientras que un producto de software para ser explotado durante un largo período (10 años o más) necesita ser confiable, mantenible y flexible para disminuir los costos de mantenimiento y perfeccionamiento durante el tiempo de explotación. 102 Capitulo 6 – Software de Calidad. Aspectos de la Calidad La Calidad del Software puede medirse después de elaborado el producto. Pero esto puede resultar muy costoso si se detectan problemas derivados de imperfecciones en el diseño, por lo que es imprescindible tener en cuenta tanto la obtención de la calidad como su control durante todas las etapas del ciclo de vida del software. Objetivo de la Calidad en los Sistemas El Objetivo que persigue la Calidad en los Sistemas está orientada a: • • • • • • • Incrementar la productividad y satisfacción al trabajo de los profesionales afines al campo de la computación. Mejorar la calidad del producto del software. Proveer técnicas aplicadas para automatizar el manejo de datos. Realizar una planeación eficaz de los sistemas. Documentar. Validar y controlar formalmente la calidad del trabajo realizado. Cumplir con los objetivos de la organización en cuanto a productividad de sus sistemas de cómputo. Obtención de un Software de Calidad La Obtención de un Software con Calidad implica la utilización de metodologías o procedimientos estándares para el análisis, diseño, programación y prueba del software, que permitan uniformar la filosofía de trabajo, en aras de lograr una mayor confiabilidad, mantenibilidad y facilidad de prueba, a la vez que eleven la productividad, tanto para la labor de desarrollo como para el control de la Calidad del Software. El principio tecnológico define las técnicas a utilizar en el proceso de desarrollo del software. El principio administrativo contempla las funciones de planificación y control del desarrollo del software, así como la organización del ambiente o centro de ingeniería de software. El principio ergonómico define la interfaz entre el usuario y el ambiente automatizado. Para el aseguramiento de la calidad es necesario su control o evaluación. Control de la Calidad El Control de la Calidad es realizar una observación constante acerca del cumplimiento de las tareas que pueden ofrecer una calidad objetiva a la forma en como se está desarrollando un proyecto de Ingeniería de Software. Es decir, una vigilancia permanente a todo el proceso de desarrollo y ciclo de vida del software. Esta meta puede alcanzarse mediante frecuentes inspecciones a las metodologías de 103 Capitulo 6 – Software de Calidad. trabajo y uso de herramientas, revisiones de prototipos y evaluación exhaustiva de los productos finales. El Control de la Calidad permite realizar las rectificaciones pertinentes al desarrollo en cuanto éste empieza a desviarse de sus objetivos, por lo tanto, de la calidad del trabajo. Estas rectificaciones son posibles gracias a una retroalimentación de las etapas superiores, creado así un aprendizaje al observar las salidas de cada etapa, hasta el producto final, y mejorar los procesos que dan origen al sistema. En el Control de Calidad deben tenerse presentes los costos que esta involucra. Si se piensa en las tareas que deben realizarse en este control, puede observase que es necesario llevar a cabo tareas de búsqueda de problemas, evaluación, realimentación, rectificación, elaboración, modificación y estudio de la documentación; entre otras actividades. Pero debe existir un compromiso, ya que un excesivo costo en el control de la calidad puede hacer que este proceso se torne ineficiente. Pero, por otra parte, el mejoramiento de la calidad implica reducir los costos ya que se tendría un cierto nivel de calidad ya asegurado. Finalmente, y como consecuencia de la naturaleza del proceso de desarrollo de productos software, el asegurar la calidad en las primeras etapas de este involucra que los costos del control en las etapas posteriores tenderá a disminuir al tener menos aspectos que controlar pues, nuevamente, la calidad estaría asegurada en sus bases. Control de Calidad del Software Para controlar la Calidad del Software es necesario, definir los parámetros, indicadores o criterios de medición. El software posee determinados índices mensurables que son las bases para la calidad, el control y el perfeccionamiento de la productividad. Una vez seleccionados los índices de calidad, debe establecerse el proceso de control, que requiere los siguientes pasos: 1. Definir el software que va a ser controlado: clasificación por tipo, esfera de aplicación, complejidad, etc., de acuerdo con los estándares establecidos para el desarrollo del software. 2. Seleccionar una medida que pueda ser aplicada al objeto de control. Para cada clase de software es necesario definir los indicadores y sus magnitudes. 3. Crear o determinar los métodos de valoración de los indicadores: métodos manuales, como cuestionarios o encuestas estándares para la medición de criterios periciales y herramientas automatizadas para medir los criterios de cálculo. 4. Definir las regulaciones organizativas para realizar el control: quiénes participan en el control de la calidad, cuándo se realiza, qué documentos deben ser revisados y elaborados, etc. 104 Capitulo 6 – Software de Calidad. Indicadores para diferenciar los productos de calidad de los que carecen de ella: • • • El acercamiento a cero defectos. El cumplimiento de los requisitos intrínsecos y expresos. La satisfacción del cliente Sobre todo la satisfacción del cliente. La Calidad del Software debe ser una disciplina más dentro de la Ingeniería del software. El principal instrumento para garantizar la calidad de las aplicaciones sigue siendo el Plan de Calidad. El plan se basa en unas normas o estándares genéricos y en unos procedimientos particulares. Las normas, directivas, modelos y estándares son básicamente las siguientes: • Familia de normas ISO 9000 y en especial, la ISO 9001 y la ISO 9000-3.2: 1996 Quality Management and Quality Assurance Standards • ISO 8402: 1994 • IEEE 730/1984, Standard for Software Quality Assurance Plans • IEEE Std 1028: 1989, IEEE Standard for Software Reviews and Audits • CMM. Capability Maturity Model • ISO/IEC JTC1 15504. SPICE. Software Process Improvement and Capability Determination. • Modelo de EFQM. Modelo de la Fundación Europea de Gestión de Calidad Los procedimientos pueden variar en cada organización, pero lo importante es que estén escritos, personalizados, adaptados a los procesos de la organización y, lo que es más importante, que se cumplan. La Calidad del Software debe implementarse a lo largo de todo el ciclo de vida, debe correr paralela desde la planificación del producto hasta la fase de producción del mismo. Capacidades del Software Las capacidades importantes para un producto de software desde el punto de vista del usuario, así como los factores que determinan la calidad de cada una de las capacidades son: • • • 105 Capacidad de operación con los factores de corrección, facilidad, eficiencia, integridad y facilidad de uso. Capacidad para ser modificado o de revisión con los factores de flexibilidad, facilidad de prueba y facilidad de mantenimiento. Capacidad de transición o de adaptación a otros entornos con los factores de transportabilidad, capacidad de reutilización y de interoperación. Capitulo 6 – Software de Calidad. Calidad Total en el Proceso de Desarrollo del Sistema Para alcanzar la "Calidad Total", es necesaria la satisfacción por parte de los elementos que intervienen en el proceso: • • • La satisfacción de la alta dirección La satisfacción del personal involucrado en el desarrollo del sistema La satisfacción del usuario final La aplicación del control de calidad de sistemas no es solamente al sistema en sí, ésta conforma la última parte de la evaluación. Elementos para el Proceso de Sistemas Son tres los elementos que integran el proceso de sistemas, los cuales por su importancia deben de considerarse para el mejor control de calidad y realización de los sistemas, estos son: 1. Gente 2. Herramientas (Software y Hardware) 3. Tiempo disponible Debe contarse con la gente adecuada, que tenga la suficiente capacidad para realizar el trabajo, las herramientas de trabajo deben ser confiables, no limitadas y debe tomarse en cuenta cuanto tiempo se dispone para la elaboración del sistema. Componentes para la Calidad Total • • • • • • • • • • Claridad Involucración Planeamiento Estándares Entrenamiento Experiencia Controles Documentación Soporte Finalización Claridad La definición de lo que se tiene que hacer o lo que el usuario necesita, debe ser clara para todos los responsables del proyecto, esto con el fin de establecer reglas a seguir. Involucración Es necesario revisar cada etapa del proyecto. Para cada una de ellas es importante dejar en claro si vale la pena continuar o no, si hay limitaciones o restricciones que afecten o impidan el buen funcionamiento del proyecto y si se está 106 Capitulo 6 – Software de Calidad. dando el cubrimiento adecuado a todos los requerimientos y funciones, divisiones o departamentos que están involucrados en la realización del proyecto. Planeamiento En la planificación intervienen tanto los usuarios como el personal que desarrolle el proyecto. Debe planearse el grado de integración que se requiere en las diferentes áreas de la organización, para interpretar necesidades o requerimientos satisfactorios con el objeto de llegar a acuerdos en caso de imprevistos en asuntos tan simples como el método mediante el cual vamos a poner a trabajar alguna etapa o actividad en el proyecto. Estándares Tiene que tomarse en cuenta la forma mediante la cual vamos a trabajar desde el punto de vista tecnológico, ya que es necesario tener presente la definición de diversos factores que afectarían la realización del proyecto, como son: • • • • • • • • • Lenguaje de programación Manejo de librerías Código Instrucciones Comentarios Administración de backups Administración de archivos Periodicidad de revisiones Documentación Debe quedar clara su definición, los elementos que los deben integrar, así como su estandarización. Sin una estandarización el proyecto se vendría abajo. Entrenamiento El entrenamiento es un factor determinante para la realización de un proyecto, ya que mediante él se obtienen los conocimientos y habilidades que se aplicarán en el proyecto. Experiencia El contar con mucha o poca experiencia, determina el tiempo de desarrollo del sistema así como la calidad del trabajo que realice (oportunidad - calidad). Controles Los controles que se establezcan deben realizarse con alguna periodicidad. Primeramente debe verse el avance del proyecto; el cumplimiento de los requerimientos del cliente; el seguimiento y las normas de seguridad y de auditoria; la involucración de las personas clave para el proyecto; el funcionamiento de la aplicación; del desarrollo con el usuario y un punto importante es la mutua satisfacción entre de la gente que realiza el proyecto con el usuario. 107 Capitulo 6 – Software de Calidad. En este caso puede contarse con un Calendario de Entrega donde contenga los puntos anteriormente mencionados, así como contar con Bitácoras que respalden las reuniones que se tengan con los usuarios y los acuerdos a los que hayan llegado ambas partes. Documentación Es importante que la documentación que se genere debe ser clara y útil en cuanto al sistema, ya sean los programas, las tareas del usuario y sus procedimientos, el manejo de la aplicación y producción y los cuidados con respecto a back-ups, ayudas y soporte requerido, la bitácora de historia respecto a fallas, mejoras, arreglos, etc., contribuyen a una mejor utilización del sistema, es decir, a una mayor calidad en su operación. Manuales de usuario, técnico y de operación. Soporte Es indispensable tener claro quién nos puede apoyar en las áreas técnica, de análisis, en el área usuaria, ya sea en la instalación, en el área ejecutiva o en algún otro aspecto, ya que las aplicaciones en los proyectos de sistemas enfrentan siempre necesidades críticas de soporte. Finalización La finalización del proyecto de un sistema es una de las labores más importantes en el desarrollo del mismo, de igual manera que en cada etapa. En la finalización del proyecto es necesario considerar cinco puntos vitales: • • • • • La revisión de todos los pasos realizados y de las etapas incluidas en el proceso total; Elaboración del proceso en forma integral; Calidad hecha en cada uno de los pasos o etapas del proyecto; La atención a los requerimientos del usuario en términos de hacer todo lo que él quiera, o más; Y, por supuesto, la satisfacción de nuestro usuario o cliente que, en últimas, es el reconocimiento a la labor bien realizada y de alta calidad. Cuando se desarrolle un sistema o aplicación y se instale, debe asegurarse de hacerlo de tal manera que lo que se entregue esté completo, sea oportuno, no tenga errores, sea confiable, útil y estable. Administración de la Calidad Calidad en el Nivel de Organización La Administración de la Calidad cuenta con dos niveles de trabajo: El nivel de entidad u organización Donde se trata de crear y gestionar una infraestructura que fomente la calidad de los productos software mediante la adecuación y mejora de las actividades y 108 Capitulo 6 – Software de Calidad. procesos involucrados en su producción e, incluso, en su comercialización y en la interacción con los clientes. El nivel del proyecto Donde las guías que la infraestructura organizativa prevé para las distintas actividades y mantenimiento del software deben ser adaptadas a las características concretas del proyecto y de su entorno para ser aplicadas a la práctica. Dentro del primer nivel de acción, la gestión de la calidad en organizaciones de software ha seguido dos líneas que pueden ser complementarias entre sí: Por una parte, se ha seguido la línea marcada por las entidades internacionales de estandarización para todas las organizaciones de producción o servicios. Principalmente se han impuesto en la práctica las directrices marcadas por ISO (Organization for International Standardization) a través de unas normas ISO 9000 para la gestión de calidad. En el caso del software es principalmente aplicable la norma ISO 9001. El sector de software difiere por la naturaleza del producto tanto del resto de sectores productivos que ha sido necesario crear una guía específica para su aplicación a este sector: ISO 9000-3. El mundo del software ha creado sus propias líneas de trabajo en la gestión de la calidad, trabaja sobre los procesos de producción como medio para asegurar la calidad del producto software. Por ejemplo, el SEI (Software Engineering Institute), proponiendo un modelo de clasificación y mejora de los procesos empleados por las organizaciones de software denominado CMM (Ver anexo). Calidad en el Nivel de Proyecto La Calidad del Software se diseña conjuntamente con el sistema, nunca al final. A mayor calidad, mayores son los costos, pero mayores también los beneficios obtenidos en la fase del mantenimiento del software. Este costo hay que considerarlo dentro de todo el ciclo de vida del proyecto. El aseguramiento de la Calidad de Software engloba un enfoque de gestión de calidad, tecnología de ingeniería de software efectiva (métricas y herramientas), revisiones técnicas formales que se aplican durante el proceso del software, una estrategia de prueba multiescalada, el control de la documentación del software y de los cambios realizados, un procedimiento que asegure un ajuste a los estándares de desarrollo del software (cuando sea posible) y mecanismos de medición y de generación de informes. La idea de la necesidad de realizar pruebas en fases tempranas, conduce a realizar un mecanismo básico que permite las Revisiones Técnicas Formales. Una Revisión Técnica Formal (RTF) es una actividad que garantiza la Calidad del Software y que es llevada a cabo por los profesionales de la ingeniería de software. Los objetivos de la RTF son: 1. Descubrir errores en la función, la lógica o la implementación de cualquier representación del software; 109 Capitulo 6 – Software de Calidad. 2. Verificar que el software bajo revisión alcance sus requisitos; 3. Garantizar que el software haya sido representado de acuerdo con ciertos estándares predefinidos; 4. Conseguir un software desarrollado de forma uniforme, y 5. Hacer que los proyectos sean más manejables. La Prueba del Software Se pueden realizar inspecciones para cada módulo o pequeño grupo de módulos que conformen un sistema. Al limitar el centro de atención de la RTF la probabilidad de descubrir errores es mayor. En la figura se puede apreciar lo que sería el flujo de información necesario para realizar correcta y completamente una prueba de software, la cual puede ser aplicada tanto a los módulos individuales como al software en su totalidad. Calidad por Etapas Calidad en el Diseño Aquí se plantean características definidas para la realización del producto software que deberán cumplirse posteriormente. La calidad se basa en definir un listado de especificaciones a seguir. Involucra descripción de los procesos, tareas y responsabilidades de los equipos de desarrollo. 110 Capitulo 6 – Software de Calidad. En esta etapa la calidad aumenta en la medida en que se realiza una alta especificación de los procesos y se propone una estrecha tolerancia a la modificación, estableciendo los métodos correctivos a las desviaciones ocurridas. Esta es la medida de la calidad apreciada por los usuarios finales del entendimiento del producto software. Estas apreciaciones de calidad hacia un determinado producto elevarán el nivel de confianza para la organización desarrolladora, lo que puede elevar su posición en el mercado. Métricas del Software Las métricas son escalas de unidades sobre las cuales puede medirse un atributo cuantificable. Cuando se habla de software nos referimos a la disciplina de recoger y analizar datos basándonos en mediciones reales de software, así como a las escalas de medición. Clasificación de Métricas Las métricas de software se pueden clasificar como: Métricas orientadas a la función y Métricas orientadas al tamaño. También se pueden clasificar según la información que entregan: 1. Métricas de productividad, las que se centran en el rendimiento del proceso de ingeniería de software. 2. Métricas de calidad, proporcionan una indicación de cómo se ajusta el software a los requisitos explícitos e implícitos del cliente. 3. Métricas técnicas, que se centran más en el software que en el proceso a través del cuál se ha desarrollado (por ejemplo grado de modularidad o grado de complejidad lógica). Estándares y Modelos de Calidad en la Ingeniería de Software Introducción La estandarización es toda actividad documentada que norma el comportamiento de un grupo de personas. Los estándares nos dan los medios para que todos los procesos se realicen siempre de la misma forma, mientras nos surjan ideas para mejorarlos. Son nuestra guía para la productividad y la calidad. Expectativas de los estándares: • • • • 111 Mejora de procesos de software acorde a los objetivos estratégicos Mejora de los productos Protección del cliente o usuario Protección de la organización (cultura de la organización y mejora continua) Capitulo 6 – Software de Calidad. Existen varias organizaciones de estandarización internacional, algunas son regionales mientras que otras son globales. Las últimas están relacionadas con la ONU o son independientes, como por ejemplo la International Telecommunication Union (ITU). La International Electrotechnical Commission (IEC) que fue fundada en el año 1906 para definir estándares en eléctrica y electrónica, mientras que la International Organization for Standardization (ISO) fue creada en 1947 para abarcar otros temas. Ambas tienen por objetivo facilitar el intercambio de bienes y servicios a nivel internacional, entre otras. En 1987, ISO e IEC decidieron formar el Joint Technical Commit (JTC), cuyo objetivo es elaborar estándares para la tecnología de información Information Technology (IT). Organizaciones como la ISO, BOOTSTRAP, entre otras se han dedicado a crear modelos para mejorar la Calidad del Software, entre ellos tenemos: • • • • • • ISO 9000-3 Tick IT (Inglaterra) CMM (Estados Unidos) Bootstrap (Europa) Trillium (Canadá) ISO/SPICE (Australia) La Norma ISO 9000 La Organización Internacional para la Estandarización (ISO) fue fundada el 23 de febrero de 1947 con el objetivo de crear una norma internacional de calidad. El Comité Técnico ISO/TC 176 para Aseguramiento de la Calidad fue el encargado de crear el estándar ISO 9000. El objetivo del estándar es desarrollar un código mínimo que contenga prácticas de administración para garantizar el Aseguramiento y Administración de la Calidad, es decir, qué hacer para responder a los requerimientos de un mercado cada vez más competitivo y cómo deben responder los proveedores y compradores respecto a la calidad de los bienes o servicios intercambiados. Para ello establece una serie de guías para la selección y uso del estándar deseado, así como aclara conceptos en cuanto a la calidad y las interrelaciones que se establecen. Estructura General De forma general la norma se divide en 4 guías o modelos fundamentales: • • • ISO 9001 Sistemas de Calidad. Modelo de Aseguramiento de la Calidad en el diseño, desarrollo, producción, instalación y servicio. ISO 9002 Sistemas de Calidad. Modelo de Aseguramiento de la Calidad para la producción, instalación y servicio. ISO 9003 Sistemas de Calidad. Modelo de Aseguramiento de la Calidad para la inspección final y pruebas. 112 Capitulo 6 – Software de Calidad. • ISO 9004 Elementos para la Administración y el Sistema de Calidad. Guía para el Sistema de Aseguramiento de la Calidad. Además, existen otras normas y entre ellas las más relevantes son: • • • ISO 9000-1 Guía para la selección de la norma a usar. ISO 8402 Recopilación de definiciones. Vocabulario. ISO 9000-3 Estándares de Administración y Aseguramiento de la Calidad. Guía para la aplicación de ISO 9001 al desarrollo, suministro y mantenimiento del software. De forma general los requerimientos fundamentales de ISO 9000 son: • • • • • • • • • Escribir un manual de calidad, describiendo el Sistema de Calidad en alto nivel. Escribir documentos en forma de procedimientos que describan cómo debe hacerse el trabajo en la organización. Crear un sistema para controlar la distribución y reedición de documentos. Diseño e implantación de un sistema de acciones preventivas y correctivas para prevenir la ocurrencia de problemas. Identificar las necesidades en cuanto a entrenamiento en la organización. Determinar las medidas y equipos para realizar las pruebas. Capacitar al personal de la organización en la operación del Sistema de Calidad. Planificar y llevar a cabo auditorias de calidad internas. Tener en cuenta los requerimientos del estándar con los que no cumple la organización. Los factores que determinan el modelo a elegir son: 1. La complejidad del proceso de diseño. Se refiere a la dificultad para diseñar el producto o servicio cuando éste no ha sido diseñado. 2. La madurez del diseño. Se proyecta hacia el conocimiento y aprobación del diseño total, ya sea por las pruebas de desempeño o por la experiencia en el campo. 3. La complejidad del proceso de producción. Está relacionado con la capacidad del proceso de producción, las necesidades de desarrollo del nuevo proceso, las variaciones que se requieren y el impacto en el desempeño del producto o servicio. 4. Las características del producto o servicio. Depende de la complejidad del producto o servicio, del número de características interrelacionadas y de su influencia en el desempeño. 1. La seguridad del producto o servicio. Relacionado con el riesgo de ocurrencia de fallas y el impacto de éstas. 2. Económico. Se refiere al incremento de los costos, para el suministrador o comprador, que puede provocar desacuerdos en cuanto al producto o servicio. 113 Capitulo 6 – Software de Calidad. El Modelo Tick IT El Departamento de Comercio e Industria del Reino Unido (DTI: Department of Trade and Industry) creó el esquema Tick IT. Los objetivos primordiales de éste fueron, además de desarrollar un sistema de certificación aceptable en el mercado, estimular a los desarrolladores de software a implementar sistemas de calidad, dando la dirección y guías necesarias para tal efecto. Aunque el proyecto original estuvo a cargo del DTI1, la responsabilidad actual por el esquema Tick IT se pasó a DISC, que es una oficina dependiente de British Standards Institution (BSI) Standards Division, siendo esta última la única autoridad en el Reino Unido para publicar estándares. Ciclo de Vida del Software Un sistema de calidad típico Tick IT deberá contener los elementos que se enlistan a continuación: • • • • • • • • • • • • • • • Elaboración de propuestas y revisión de contratos asegurando que todos los requerimientos estén bien especificados y de que la organización tiene la capacidad para cumplirlos. Análisis y especificación de los requerimientos del sistema asegurando que sean revisados y acordados con el cliente. Planeación, control y monitoreo del avance del desarrollo respecto al plan comunicando a todas las partes afectadas y que avise oportunamente de problemas potenciales. Planeación de la calidad del proyecto, especificando las inspecciones, revisiones y pruebas requeridas durante el desarrollo. Inspecciones de los productos contra estándares y requerimientos aplicables y las acciones correctivas correspondientes. Diseño de primer nivel identificando los componentes principales y los requerimientos que satisfacen. Diseño detallado de todos los componentes e interfaces, construcción, y prueba de los mismos verificando que satisfagan la especificación. Integración, pruebas e inspecciones del sistema, demostrando que el sistema integrado funciona correctamente y satisface su especificación. Identificar, segregar, investigar y corregir productos no conformes. Auditorias, pruebas e inspecciones de aceptación del sistema demostrando al cliente que el sistema satisface los requerimientos. Almacenamiento, replicación, envío e instalación, asegurando la integridad y seguridad de los productos, así como el cumplimiento de los compromisos adquiridos con el cliente. Puesta en marcha y liberación del producto para disponibilidad del cliente. Entrenamiento a usuarios en el uso del sistema de tal manera que pueda operarlo y beneficiarse completamente del mismo con la mínima intervención del proveedor. Mantenimiento o sustitución del sistema, asegurando que se continúa operando en conformidad con los requerimientos del cliente o usuario. Soporte a clientes de acuerdo a lo especificado en el contrato. 114 Capitulo 6 – Software de Calidad. Soporte y Aseguramiento de Calidad • • • • • • • • • • • • Establecer políticas y objetivos de calidad generales de la organización que sirvan para alinearla en todas sus actividades, procedimientos y políticas específicas. Implantar y mantener un sistema de aseguramiento de calidad. Auditorias, revisiones y acciones correctivas al sistema de calidad que aseguren que el sistema cumple con los requerimientos, es utilizado y que es efectivo en el logro de resultados. Definir, recolectar y analizar datos de calidad para evaluar la efectividad del sistema de calidad e identificar mejoras potenciales. Administración de la organización y los proyectos de tal forma que facilite los resultados de calidad. Administración de la configuración que identifique y controle, de manera continua, las partes constituyentes y sus versiones, de cada instancia de un sistema o subsistema. Respaldos, seguridad y almacenamiento que protejan contra cualquier pérdida o corrupción. Sistema de control de registros y documentación para todas las actividades de aseguramiento de calidad, de los proyectos y de soporte, incluyendo procedimientos y registros. Especificación y control del proceso de desarrollo incluyendo técnicas, prácticas, convenciones, estándares, mediciones y estadísticas. Proceso de compras, incluyendo identificación, selección, adquisición y aceptación que asegure que los bienes y servicios adquiridos sean como se requiere y de calidad aceptable. Control de productos incluidos, equipo y herramientas utilizadas: Hardware o Software, adquiridos o suministrados por el cliente, incluyendo utilización, configuración, seguridad. Entrenamiento, reclutamiento y desarrollo de personal que asegure su competencia y motivación, y disminuya su rotación. El Modelo CMM A principios de los años 80’s el Departamento de Defensa de los Estados Unidos enfocó sus tareas a la revisión de los problemas del software y a su mejoramiento. Para contribuir a este programa se creó el Instituto de Ingeniería de Software (SEI) a finales de 1984. Como parte de su trabajo, el Instituto se dio a la tarea de desarrollar el Modelo de Madurez del Proceso de Software y para 1986 se comenzó el Proyecto de Evaluación de la Capacidad del Software. Después de varios años de realizar cuestionarios, evaluaciones, consulta e investigación, junto a otras organizaciones, en 1991 SEI produce el Modelo de Capacidad y Madurez del Software. El Modelo de Madurez y Capacidad del Proceso de Software (CMM) ayuda a que las organizaciones para producir de manera consistente y predecible productos de calidad superior. La capacidad del proceso es la habilidad inherente para producir los resultados planeados. El principal objetivo de un proceso de software maduro es el de producir productos de calidad que cumplan los requerimientos del usuario. 115 Capitulo 6 – Software de Calidad. Cuando se habla de madurez se entiende como el crecimiento alcanzado en la capacidad del proceso de software y que se considera como una actividad a largo plazo. En una organización de software inmadura, el proceso de software es generalmente improvisado, no existen planes rigurosos, se enfocan en resolver las crisis que se le presentan, carecen de bases objetivas para enjuiciar la calidad de los productos o para resolver los problemas. Por lo contrario cuando la organización alcanza cierto grado de madurez posee una gran habilidad para administrar el proceso de desarrollo y mantenimiento del software, se hacen pruebas y análisis de costo-beneficio para mejorar el proceso, el administrador monitorea la calidad del producto y la satisfacción del cliente, se llevan registros y todos los integrantes están involucrados. La madurez del proceso de software esta dada cuando un proceso en específico es explícitamente definido, administrado, medido, controlado y es efectivo. El ciclo Shewhart propone las bases para el trabajo de mejoramiento del proceso. Este consta de 4 pasos que se repiten en forma de ciclo hasta que la implantación produce los resultados esperados y los cambios pasan a ser permanentes. Los pasos son: 1. Planear a. Definir el problema b. Establecer los objetivos a mejorar 2. Ejecutar a. Identificar las posibles causas de problemas b. Establecer las bases c. Probar los cambios 3. Revisar a. Recolectar los datos b. Evaluar los datos 4. Actuar a. Implementar los cambios b. Determinar la efectividad CMM es un modelo descriptivo en el sentido que describe los atributos esenciales que se espera caractericen una organización dentro de un nivel de madurez en particular. Es un modelo normativo ya que las prácticas detalladas caracterizan el tipo normal de comportamiento que se espera de una organización que realiza proyectos a gran escala. No es prescriptivo ya que no dice a la organización como mejorar. Estructura del modelo. CMM: Marco de Trabajo Capability Maturity Model – SEI 116 Capitulo 6 – Software de Calidad. El modelo consta de 5 niveles, diseñados de forma que los inferiores proveen unos fuertes cimientos incrementados de manera progresiva sobre los que se construyen los niveles superiores. Estas 5 etapas de desarrollo son referidas como niveles de madurez y en cada una la organización alcanza una capacidad en el proceso superior. Los 5 niveles del modelo son: 1. Inicial: el proceso de software es un proceso improvisado y caótico. Pocos procesos están definidos y el éxito que se pueda obtener depende de las habilidades, conocimientos y motivaciones del personal. No existen calendarios ni estimados de costos y las funcionalidades y calidad del producto son impredecibles. No existe un ambiente estable para el desarrollo y mantenimiento del software. El proceso del software es impredecible por el continuo cambio o modificación a medida que avanza el trabajo. 2. Repetible: se establecen procedimientos de administración del proceso básico para determinar costos, calendarios y funcionalidades. Se establecen las políticas para la administración del proceso y los procedimientos de implantación. El proceso se basa en repetir éxitos anteriores en proyectos de similares características, por lo que los mayores riesgos se presentan cuando se enfrentan a nuevos proyectos. Se exhiben problemas de calidad y carecen de una adecuada estructura para mejorarla. 3. Definido: el proceso de software para las actividades administrativas y técnicas está documentado, homogeneizado e integrado en un proceso de software estándar dentro de la organización, que ayudará a obtener un desempeño más efectivo. El grupo que trabaja en el proceso enfoca y guía sus esfuerzos al mejoramiento de su desarrollo, facilita la introducción de técnicas y métodos e informa a la administración del estado del proceso. La capacidad del proceso está basada en una amplia comprensión común dentro de la organización de las actividades, roles y responsabilidades definidas en el desarrollo de software. 4. Administrativo: se recolectan medidas detalladas del proceso de software y de la calidad del producto. Ambos son cuantitativamente entendidos y controlados. El ciclo de Shewhart es constantemente utilizado para planear, implementar y registrar las mejoras al proceso. Este nivel de capacidad permite a la organización predecir las tendencias en la calidad del producto dentro de los límites establecidos y tomar las acciones necesarias en caso que sean excedidos. Los productos de dicha categoría son predeciblemente de alta calidad. 5. Optimización: el mejoramiento continuo retroalimentación cuantitativa y desde las innovadoras. La organización tiene los débiles y conocer como fortalecerlos. Su causas de defectos y su prevención. 117 del proceso es garantizado por la pruebas de técnicas y herramientas medios para identificar los puntos actividad clave es el análisis de las Capitulo 6 – Software de Calidad. El Modelo BOOTSTRAP El Instituto Bootstrap es una organización no lucrativa dedicada a la mejora continua del modelo de calidad de software llamado BOOTSTRAP, también tiene como propósito ayudar a la industria europea del software para mejorar su competitividad. Bootstrap es un método para analizar, rediseñar y mejorar los procesos de negocio del desarrollo de software. Este se compone de: un modelo, un proceso de evaluación, una base de datos de soporte, un proceso de mejora y los instrumentos de evaluación. Su enfoque es evaluar el proceso, no el producto. Para eso se definen un conjunto de características para los procesos, provee un análisis cuantitativo, produce vistas analíticas, hace evidente fortalezas y debilidades, identifica áreas de mejora, provee recomendaciones y sugiere un plan de implementación. El modelo define el paradigma Organización-Metodología- Tecnología que se usa en Bootstrap para los niveles de evaluación y agrupación de resultados. El modelo Bootstrap se basa en evaluar las unidades de producción de software de la organización, a través de sus proyectos para hacer un cambio a toda la organización. Dentro de este proceso, hay cuatro etapas principales: preparación, ejecución de la evaluación, determinación del nivel de madurez y capacidades, y la presentación de resultados de la evaluación. En la etapa de preparación se realizan las siguientes tareas: 1. un entrenamiento inicial para tener claros los objetivos 2. se seleccionan los proyectos a ser evaluados para obtener la mejor cobertura de la UPS 3. se define el personal de evaluación para minimizar la subjetividad 4. se define el personal a ser evaluado para obtener la mejor cobertura de los roles involucrados en los proyectos seleccionados y 5. se hace el acuerdo de confidencialidad. En la etapa de ejecución, las tareas son: 1. una breve reunión de apertura, para obtener un enfoque colaborativo con el personal a ser entrevistado; 2. el llenado de los cuestionarios con características generales de la UPS; 3. el llenado de los cuestionarios del proyecto elegido, incluyendo la evaluación de cómo el proceso de producción es aplicado; 4. revisión preliminar de la evaluación, y 5. reunión final, con el enfoque de presentar los resultados de la evaluación y obtener el consenso para poder pasar a la fase de mejoras. 118 Capitulo 6 – Software de Calidad. En la etapa de determinar el nivel de madurez y capacidades, es donde se califica cada pregunta con uno de 5 valores posibles: nulo, débil, regular, extenso o no aplica. Para cada atributo clave se obtiene un nivel de madurez, aplicando un algoritmo numérico, dando como resultado uno de estos niveles: 1-inicial, 2-repetible, 3-definido, 4-administrado o 5-optimizado. Estos niveles de madurez están subdivididos en cuatro, de forma que se obtenga una calificación más exacta. Los procesos de organización y metodología se califican de 1 a 5, mientras que el de tecnología se califica sólo con dos niveles A o B. Como resultado de la evaluación, la organización recibe 2 reportes, uno con los resultados de la evaluación de la UPS y otro con los resultados del proyecto evaluado. El correspondiente a la UPS contiene información como: un resumen ejecutivo, los objetivos de la UPS, los puntos débiles y fuertes, un plan de acción recomendado, etc. El reporte del proyecto contiene: comentarios del proyecto actual detallando lo referente a la organización, metodología y tecnología, los niveles de madurez para el proyecto, el plan de acción recomendado, etc. Uso de las Bases de Datos de Soporte Una de las características principales de Bootstrap es la base de datos con que cuenta para hacer análisis. Con esto se fundamenta el plan de mejoras, se pueden medir las adaptaciones a la metodología, se puede comparar contra la industria y se pueden establecer objetivos basándose en la competencia. Proceso de Mejora Otra parte importante de la metodología de Bootstrap, es el plan de mejora que sugiere. El proceso para obtener el plan de mejora es, Primero evaluar las necesidades de la organización tomando en cuenta las mejoras deseadas e indicadores sobre calidad del producto y servicio, tiempo de desarrollo, costos y riesgos del producto y del proyecto. Enseguida hacer una revisión y análisis de resultados de la evaluación, tomando en cuenta las fortalezas y debilidades detectadas. Después definir las capacidades a mejorar, considerando un período entre 18 y 24 meses. Enseguida, definir las prioridades de acuerdo a un análisis de impactos. Finalmente sobre la base de las actividades definidas, modificar la organización y responsabilidades para iniciar el cambio, estableciendo un marco de tiempos para su desarrollo y evaluación. 119 Capitulo 6 – Software de Calidad. El Modelo ISO/SPICE Software Process Improvement and Capability Determination La Organización Internacional para la Estandarización (ISO) creó el grupo de trabajo WG 10 y le encomendó el desarrollo del estándar internacional de Valuación de Procesos de Software. El grupo de trabajo (Working Group) WG 10, empezó a trabajar en enero de 1993 bajo la dirección de Alec Dorling y Peter Simms, y decidieron crear el proyecto SPICE Software Process Improvement and Capability Determination; la E de SPICE correspondía, originalmente, a "evaluation" y fue cambiado porque en algunos idiomas se traducía equivocadamente. Una de las características sobresalientes de este proyecto de estandarización es que incluyó un periodo de pruebas del estándar de 2 años. Es decir, antes de ser publicado como estándar se había estado ajustando por la práctica. Arquitectura del Modelo El modelo es tridimensional: la primera dimensión es funcional (procesos), la segunda de capacidades (niveles), y la tercera de adecuación o efectividad (calificaciones). La dimensión PROCESO: Está organizada jerárquicamente de la siguiente manera: CATEGORÍA DE PROCESOS, que agrupan procesos comunes; PROCESOS, que logran propósitos técnicos; PRÁCTICAS BÁSICAS, operaciones que conforman un proceso. Las categorías definidas son: Cliente-Proveedor, Ingeniería, Proyecto, Organización y Soporte. La dimensión CAPACIDAD: Está organizada ordinalmente en niveles de capacidad y se han definido cinco niveles. En la versión 1.0 estos niveles son: Desempeño informal, Planeación y seguimiento bien definido, Cuantitativamente controlado, Mejora continua. Está en etapa de instrumentación una versión 2.0. La tercera dimensión es la CALIFICACIÓN: El juicio mismo: ¿qué calificación le doy a este proceso en este atributo de capacidad?. Las escalas que se manejan son discretas de tipo: 0 = No adecuado, 1 = Parcialmente adecuado, 2 = Muy Adecuado, 3 = Totalmente Adecuado. Los Elementos de Evaluación Marco de Valor El Modelo ISO/SPICE tiene un marco de valor explícito. 120 Capitulo 6 – Software de Calidad. En la dimensión funcional o de proceso: las "mejores prácticas". En la dimensión de capacidad: los atributos de proceso o prácticas genéricas que incrementarán la capacidad del proceso. Este es uno de los componentes más valiosos del estándar internacional. Evidencia La evidencia para la evaluación serán los productos producidos por las prácticas base. Éste es un enfoque de efectividad… "por sus frutos los conoceréis…". Cada producto tipo ha sido catalogado y sus características de calidad definidas. Este es otro elemento filosófico fundamental de ISO/SPICE: no es un modelo nominalista. Recurrencia Por último el elemento recurrencia, para fundamentar un juicio o evaluación vendrá dada por la selección de instancias de proyectos o productos representativas, a juicio del Evaluador, de las capacidades reales del proceso de software. Conceptualmente el modelo ISO/SPICE es un modelo inductivo en su parte funcional: de característica a producto, de producto a práctica, y de práctica a proceso. En su parte de capacidades es un modelo evolutivo. Es, en general, un modelo realista: va a ver los productos, es decir, la efectividad de los procesos no lo que está escrito en algún manual de calidad o de procesos. Estándares y modelos de evaluación y mejora de los procesos software. 121 Capitulo 6 – Software de Calidad. 122 Capitulo 6 – Software de Calidad. CONCLUSIONES Es casi imposible resumir y condensar en pocas páginas la gran cantidad de información disponible sobre el tema. A modo de ejemplo de esos volúmenes, sobre el tema “Programación Orientada a Objetos” en Internet quiero destacar: Buscador Google Yahoo Altavista Proveedor de libros Amazon.com Sun Microsystems Sitios encontrados En castellano En cualquier idioma 110.000 8.560.000 93.700 5.640.000 94.200 --volúmenes encontrados En cualquier idioma 3.484 32 Como puede verse existe mucha información sobre el tema. La misma simbología ya es una gran nebulosa de la Tecnología de Objetos (OT), pero al menos se expondrán algunas consideraciones prácticas extraídas de la aplicación con los alumnos en la cátedra Programación IV, desde el año 2000 de lenguajes, técnicas y métodos orientados-a-objetos. La elección del Lenguaje de Programación Es opinión antigua, firme y consensuada que no existe la solución ideal al elegir un lenguaje de programación, pero también se acepta de forma unánime que la Orientación a Objetos proporciona ventajas evidentes en el desarrollo genérico de software. Naturalmente lo más fácil es pensar que los "nuevos" métodos que la OO propugna van a facilitar el trabajo diario "real" en empresas y departamentos de desarrollo de software. Los atractivos de los "objetos" aparecen innegables y, las siguientes etapas personales se cubren de forma inexorable: • • • • Novicio (1-3 meses): se mezcla el código eminentemente funcional modificándolo y añadiéndole porciones de objetos. Aprendiz (3-6 meses): aquí se produce el "despertar" del que hablan los textos de Zen, se ve el verdadero significado de la orientación-a-objetos y se empiezan a bosquejar diseños pertinentes. Postulante (6-18 meses): los conceptos ya se aplican con soltura en el desarrollo, pero todavía se dan algunos problemas con la modelización. Experto (gurú): todo son objetos, y el sujeto se pregunta: ¿cómo pude pensar antes de otra manera? Este estadio no siempre se alcanza, naturalmente (afortunadamente, según algunos). Y para fundamentar, aun más la gran profusión de métodos con bases teóricas, veamos algunos de los actuales métodos de OOA/OOD (Análisis Orientado a Objetos/Diseño Orientado a Objetos): 123 Capitulo 6 – Software de Calidad. METODOS Object Oriented Design Object Behaviour Analysis Methodology for Object Oriented Software Engineering of Systems General Object Oriented Design Object Oriented Software Engineering Visual Modeling Technique Texel Object Modeling Technique Better Object Notation Object Oriented System Analysis Object Oriented Structured Design Systems Engineering OO Syntropy Object Oriented Jackson Structured Design Hierarchical Object Oriented Design Object Oriented Analysis Object Oriented Design Object Oriented System Analysis Colbert Frame Object Analysis Semantic Object Modelling Approach Berard ADM3 Ptech Object Oriented Rôle Analysis, Synthesis and Structuring Fusion Desfray Responsability Driven Design OOD OBA MOSES AUTORES Grady Booch Rubin & Goldberg Henderson-Sellers & Edwards GOOD OOSE Seidewitz & Stark Ivar Jacobson IBM IBM Texel Rumbaugh y otros Nerson Shlaer & Mellor Wasserman et al. LBMS Cook y otros Jackson OMT BOM OOSA OOSD SEOO OOJSD HOOD OOA OOD OSA FOA SOMA ESA Coad & Yourdon Coad & Yourdon Embley y otros E. Colbert Andleigh/Gretzingr Ian Graham OOA&D OORASS Berard Donald Firesmith Martin & Odell Reenskaug et al. CRC Coleman y otros Softeam Wirfs-Brock et al. La realidad es que, es muy difícil diferenciar si uno es mejor que el otro. Cada autor tiene sus flaquezas y fortalezas. La experiencia y la revisión real de la gestión de proyectos orientados-a-objetos en distintas empresas muestra que el éxito siempre ha ido acompañado de una visión particularizada de OOA/OOD acorde con la política de la empresa, las peculiaridades del equipo de desarrollo, la experiencia en determinados lenguajes de programación y la naturaleza concreta de cada proyecto, dentro de unos márgenes más o menos estables de actuación. En el ámbito académico, quiero destacar, luego de 4 años de dictar en la cátedra Programación IV el Paradigma de Objetos, las siguientes conclusiones: Los alumnos se entusiasman con el concepto, pero tienen un pequeño grado de dificultad al iniciar el diseño de los objetos, definitivamente, les cuesta un poco pensar de manera abstracta. Creo que como todo en su nivel inicial, luego su práctica 124 Capitulo 6 – Software de Calidad. profesional los pulirá con un mejor diseño. Por ahora en la cátedra pregonamos el “enseñar la técnica”. Conclusiones y Perspectivas a Futuro En la etapa de investigación bibliográfica, en la que he utilizado intensamente la fuente inagotable de Internet, he podido comprobar que hay muchísimas páginas que explican muy someramente cada concepto de POO y luego muestran un ejemplo. En este trabajo he tratado de revertir ese problema, brindando abundante material teórico, como así también ejemplos pertinentes en cada caso. Los ejemplos dados, en general, son similares a los que pueden encontrarse en la literatura específica, esto es así porque son los más sencillos para la comprensión de los conceptos. Este material permitirá que los alumnos interesados en profundizar la temática de Programación Orientada a Objeto, tengan una fuente alternativa completa, clara y precisa sobre los principales tópicos de la POO. Así también, el código provisto les permitirá visualizar rápida y fácilmente la implementación de esos conceptos. Perspectivas a futuro Cómo actividades complementarias y, en cierta manera, como una continuación de este trabajo, se propone la instrumentación del concepto “SOFTWARE DE CALIDAD” ya sea en esta cátedra o como una cátedra optativa, para afianzar contundentemente la necesidad de desarrollar software competitivo para su venta a nivel internacional. A nivel de asignatura, se esta desarrollando un sitio Web en el que se pondrá disposición de los alumnos, lo siguiente: El material de apoyo utilizado para el presente trabajo. Direcciones de sitios de descarga de manuales y tutoriales sobre la temática específica. Un foro de discusión sobre el tema POO, destinado a alumnos y docentes. El material de los trabajos prácticos, con test, resultados de los trabajos prácticos, pizarra de novedades, Otros trabajos relacionados con el tema. Con esto se pretende darle mayor difusión a la temática, promover la participación de los alumnos y los docentes del área y generar la sinergia necesaria para lograr una mejor formación de nuestros recursos humanos, que garanticen una exitosa inserción laboral, como así también, la seguridad y conocimientos necesarios para que nuestros futuros egresados se atrevan a sus propios emprendimientos, en este incipiente, exigente y apasionante mundo del desarrollo del software. Formosa, 14/05/2005 125 Glosario. GLOSARIO Abstracción por especificación: Disciplina de programación que busca que el programador defina qué es lo que hace cada uno de los módulos que componen un programa o sistema, aún antes de realizar la implementación. Abstracción por parametrización: Uso de argumentos en procedimientos y rutinas. Abstracción: Proceso por medio del que se elimina información para lograr tratar cosas diferentes como si fueran iguales. De esta forma se separan las características de un ente en dos grupos, el de las características que importa y el de las que sobran, para tomar en cuenta únicamente aquellas relevantes a la solución de un problema. Abstracción (2): ignorar aquellos aspectos que no sean relevantes al propósito actual para concentrarse más profundamente en aquellos que lo son. Asociación y mensajes: los objetos de un sistema se relacionan y se comunican entre sí. Asociación: Relación de uso en general. Atributos: Datos que caracterizan las instancias de una clase. Calificación: Limita la multiplicidad de las asociaciones. Clases: Abstracción de objetos con propiedades comunes. Composición: Relaciones todo/parte. Concatenación: Unir elementos. Constructores y Destructores: Facilidad de un lenguaje de programación que le permite al programador definir uno o varios procedimientos especiales que se encargan de inicializar y destruir las variables de un tipo, y que además son invocadas automáticamente por el compilador. En general, la misión principal de los destructores es devolver la memoria dinámica asociada a un objeto. Encapsulación: la interfase de cada componente del programa se define de forma que revele tan poco como sea posible de sus particularidades interiores. Encapsulamiento: Facilidad de un lenguaje de programación que permite definir un tipo de datos junto a sus operaciones, con el fin de obtener un tipo abstracto de datos. Enlace dinámico: es la propiedad que poseen los objetos para enviar mensajes a otros objetos sin necesidad de conocer (quizá por imposibilidad de hacerlo) la clase a la que pertenecen, se resolverá en tiempo de ejecución. Enlaces: Instancias de una relación. Relaciona instancias Especificación: Proceso por medio del que se definen las características de un ente. Generalización. Relaciones padre/hijo. Herencia (Extensión de tipos): Facilidad del lenguaje de programación que permite extender un tipo de datos, agregando al final nuevos campos. El efecto de la herencia puede simularse en la mayoría de los lenguajes tradicionales, pero el resultado es un programa menos elegante y en muchos casos mucho más difícil de programar. Herencia: expresa la similitud entre clases de objetos, mostrando la especialización de los estados y los comportamientos de las clases de objetos de nuestro sistema en forma jerárquica. Instancias: Cada uno de los objetos individuales. Iteradores: Abstracción que permite obtener todos los elementos contenidos en un tipo abstracto de datos contenedor. Los contenedores más usuales son los arreglos, las listas, los conjuntos, los árboles y en menor grado los grafos. 127 Glosario. Mensaje: Nombre del método con que se invoca a una operación sobre un objeto. Los términos método y operación son sinónimos, pero uno se usa en el contexto de Programación por Objetos y el otro en el de Abstracción de Datos. Método: Operación que es definida junto a un tipo de datos en aquellos lenguajes que soportan encapsulamiento. Módulos: Partes que componen un sistema, que generalmente se construyen de forma que sean independientes unas de otra. En general se busca que al hacer cambios en un módulo no sea necesario hacerlo en otros. En estos casos, se dice que los módulos tienen una cohesión baja. Multiplicidad: Número de instancias que intervienen en la relación. Ocultación de Datos: Facilidad de un lenguaje de programación que permite evitar que un programador que usa un tipo abstracto de datos pueda manipular la estructura interna de una instancia. De esta manera se evita que el programador usuario del tipo abstracto de datos introduzca inconsistencias en la estructura de datos. Operaciones: Funciones que pueden realizar las instancias. Polimorfismo (Funciones Virtuales): Un lenguaje de programación que soporta polimorfismo permite diferir la definición de la operación que debe aplicarse un objeto al momento en que esa operación es necesaria. De esta manera, el programador no necesita conocer el objeto sobre el que opera. Cuando se usa herencia para extender de muchas formas un tipo de datos, resulta luego conveniente que el procedimiento que se use para operar sobre un dato dependa del dato en sí, aunque el programador no haya especificado exactamente cuál es ese procedimiento. Polimorfismo: es la propiedad de dos o más clases para responder al mismo mensaje, cada una de ellas según su especificación. Procedimiento o rutina: Facilidad sintáctica de los lenguajes de programación que le permiten al programador aislar la solución de un problema particular en un módulo. Mediante el uso de procedimientos es posible aplicar la abstracción por parametrización y por especificación. Programación Estructurada: Conjunto de prácticas y disciplinas de programación que se basan en el uso de Abstracción por parametrización y por especificación para construir sistemas. Además, un lenguaje de programación soporta la Programación Estructurada si cuenta con las construcciones sintácticas IF-THEN-ELSE, WHILE-REPEAT, CASE, procedimientos y argumentos. Un programa estructurado nunca hace uso del GO TO, y generalmente se descompone modularmente como una jerarquía de procedimientos, usando la descomposición de Arriba hacia Abajo [Top-Down]. Programación Orientada a los Objetos [OOP] ó [POO]: Uso de unas técnicas de Abstracción de Datos en un lenguaje que también soporta Encapsulamiento, Herencia y Polimorfismo. Redefinición: Modificación de las propiedades heredadas. Relaciones: Se establecen entre clases. Roles: Indican los papeles de las clases en las relaciones. Sobrecarga de identificadores: Facilidad de un lenguaje de programación que permite usar el mismo identificador para procedimientos diferentes. Los procedimientos se diferencian por sus argumentos y no por su nombre. Sobrecarga de Operadores: Facilidad de un lenguaje de programación que le permite al programador definir un nuevo tipo de datos que puede usarse en expresiones. En general, la sobrecarga de operadores implica el uso de los operadores aritméticos [+ - * / ^] en expresiones. 128 Glosario. Tipos Abstractos de Datos: Es un el uso de una técnica de abstracción que busca juntar en un sólo módulo toda la programación relevante a una estructura de datos en particular. Esto se logra definiendo las operaciones de un tipo de datos. Lo usual es que un DAT provea Ocultamiento de Datos y alguna forma de Encapsulamiento de datos, aunque ésta última no sea completa. 129 Bibliografía. BIBLIOGRAFÍA • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • • 131 Patterson J., Stephenson C., Introduction to Programming in Java, University of Toronto Press, 2000 Castillo, E., Cobo A., Gómez P., Solares C. Java Un lenguaje de programación multiplataforma para Internet. Paraninfo, 1997. Newman, Alexander. Using Java. Macmillan Computer Publishing, 1996. Froufe, Agustín. Java 2 - Manual de Usuario y Tutorial. Alfaomega, 2000. Campione, Mary. The Java Tutorial. Addison-Wesley, 2000. Eckel, Bruce. Thinking in Java. Prentice Hall, 2000. Griffith, Steven W. 1001 tips para programar con Java. McGraw-Hill, 1997. Dpto Ampliación de Informática. Java y la POO. Universidad de Zaragoza España. Parra Fuente, Javier. Curso Java Avanzado. Departamento de Lenguajes y Sistemas Informáticos, Univ. Pontificia de Salamanca en Madrid Venners, Hill. Designing with interfaces. Java World (http://www.javaworld.com/), 12/1998. Grady Booch, Análisis y Diseño orientado a objetos. Ed. Addison Wesley Iberoamericana. Sitio Web Oficial de Sun Microsystem – www.java.sun.com Página de descargas de la Universidad de Castilla La Mancha http://scalab.uc3m.es/~docweb/pr-inf/index.html Revista electrónica sobre java – www.javaworld.com Tutorial Java Avanzado – www.geocities.com TUTOR JAVA – www.programacion.net Guía de Páginas OO. www.well.com/user/ritchie/oo.html#index por Ricardo Devis Guía de Páginas y Lenguajes OO. Universidad Alicante, España www.dlsi.ua.es/asignaturas/poo/Enlaces Jakarta Tomcat, http://jakarta.apache.org/tomcat/ Stevens, Jon S. “You Make the Decisión”, http://jakarta.apache.org/velocity/ymtd/ymtd.html. Hunter, J “The Problems with JSP”,http://www.servlets.com/soapbox/problems-jsp.htmp Notas específicas sobre el tema “Sobrecargas” y “Herencias” http://manowar.lsi.us.es/pipermail/csharp-dist/2001-March/000500.html http://www.sc.ehu.es/sbweb/fisica/cursoJava/fundamentos/herencia/intro_herencia.htm http://www.map.es/csi/silice/Auditr12.html http://www.sld.cu/revistas/aci/vol5_s_97/sup04197.htm http://www.openresources.com/es/magazine/softwareengineering/quality-control/ http://agamenon.uniandes.edu.co:80/sistemas/6707.htm http://gente.pue.udlap.mx/~sol/docencia/ti/sesion4.html http://www.sc.ehu.es/jiwdocoj/remis/docs/aseguracal.htm http://www.fciencias.unam.mx/~ho/SPICE/pres1.html http://www.pcworld.com.pa/1998/diciembre/novedades.html http://www.ati.es/novatica/1997/125/nv125pres.html http://www.robelle.com/smugbook/quality.html http://www.abo.fi/~atorn/SQuality/SQ11.html http://www.utexas.edu/coe/sqi/newsletter/ http://www.isaca.org/standard/sp.htm http://www.csuohio.edu/accounts/Audit4/ http://www.ati.es/gt/calidad-software/presentacion.htm Programas ejemplo. Clases abstractas. PROGRAMAS EJEMPLO DE LOS TEMAS TRATADOS. Redefinición de métodos /*Programa 1 - Versión 1*/ /* REDEFINICIÓN:(OVERRIDEN) Una clase puede redefinir (volver a * definir) cualquiera de los métodos heredados de su super-clase * que no sean final. * El nuevo método sustituye al heredado para todos los efectos * en la clase que lo ha redefinido. * Con Funciones Redefinidas en Java se utiliza siempre * Vinculación tardía */ //CLASE PADRE class Oceania { // se definen 3 constantes de tipo cadena (variables // finales) final String e1="Australia",e2="Fidji",e3="Nueva Zelanda"; public void descripción() { System.out.println("Principales Estados"); System.out.println(e1);System.out.println(e2); System.out.println(e3); } } //CLASE HIJA (derivada) class Australia extends Oceania { final String capital="Canberra"; // se define una // constante(variable final) llamada // capital y se le asigna el valor Canberra public void descripción() { System.out.println("La capital de Australia es: capital); } } "+ public class Geografia1 { public static void main(String[] args) { Australia ciudad = new Australia(); // crea un objeto "ciudad" de la clase // hija "Australia" ciudad.descripción(); // ejecuta el método "descripción" de la clase // hija } } /*el método descripción que estaba definido en la clase * padre(Oceanía), se redefine en la clase hija Australia/ 133 Programas ejemplo. Clases abstractas. /*Programa 1 - Versión 2*/ //CLASE PADRE class Oceania { //variante // se declara un vector de la clase String(cadena) y se lo // inicializa con 3 posiciones } String estado[]=new String[3]; public void descripción() { estado[0]="Australia"; estado[1]="Fidji"; estado[2]="Nueva Zelanda"; System.out.println("Principales Estados de Oceanía"); for(int i=0;i<3;i++){ System.out.println(estado[i]); } } //CLASE HIJA (derivada) class Australia extends Oceania { final String capital="Canberra";// se define una constante //(variable final) llamada “capital” y se le asigna el valor // Canberra public void descripción() { System.out.println("\n"+"La capital de Australia es: "+ capital); } } public class Geografia2 { public static void main(String[] args) { Oceania estado = new Oceania(); //(variante) Crea un objeto "estado" de la clase padre // "Oceanía" Australia ciudad = new Australia(); // Crea un objeto "ciudad" de la clase Hija // "Australia" estado.descripción(); // (variante) ejecuta el método "descripción" de la // clase padre ciudad.descripción(); //ejecuta el método "descripción" de la clase hija } } 134 Programas ejemplo. Clases abstractas. /*Programa 1 - Versión 3*/ //CLASE PADRE class Oceania { String estado[]=new String[3]; public void descripción() { estado[0]="Australia"; estado[1]="Fidji"; estado[2]="Nueva Zelanda"; System.out.println("Principales Estados"); for(int i=0;i<3;i++){ System.out.println(estado[i]); } } } //CLASE HIJA (derivada) class Australia extends Oceania { final String capital="Canberra"; // se define una constante(variable final) y se le asigna // el valor Canberra public void descripción() { System.out.println("\n"+"La capital de Australia es: "+ capital); } } //CLASE HIJA (derivada) class Fidji extends Oceania { final String capital="Suva"; // se define una constante(variable final) y se le asigna // el valor Suva public void descripción() { System.out.println("\n"+"La capital de Fidji es: "+ capital); } } public class Geografia3 { public static void main(String[] args) { /* (variante) */ Oceania ciudad; //declara una referencia a un objeto de la clase padre // "Oceania" . Se inicializa a null ciudad= new Australia(); // La referencia "apunta" al nuevo objeto creado de la // clase hija "Australia" ciudad.descripción(); // ejecuta el método "descripción" de la clase hija // "Australia" ciudad =new Fidji(); // La misma referencia "apunta" al nuevo objeto creado de // la clase hija "Fidji" ciudad.descripción(); // ejecuta el método "descripción" de la clase hija "Fidji" 135 Programas ejemplo. Clases abstractas. } } Modelos de Ejecución Versión 1: La capital de Australia es: Canberra Versión 2: Principales Estados de Oceanía Australia Fidji Nueva Zelanda La capital de Australia es: Canberra Versión 3: Principales Estados de Oceanía Australia Fidji Nueva Zelanda La capital de Australia es: Canberra La capital de Fidji es: Suva 136 Programas ejemplo. Clases abstractas. /* Programa 2 */ /* Cuando una clase deriva de otra hereda la funcionalidad de la * clase padre, pero además una clase hija, puede * sobreescribirla.Podemos escribir un método en la clase hija * que tenga el mismo nombre y los mismos parámetros que un * método de la clase padre */ class Mamifero { public void descripción() { System.out.println("El mamífero es un Vertebrado"); } } class Perro extends Mamifero { public void descripción() { System.out.println("Mamifero carnivoro "); } } public class Animales { public static void main(String[] args) { Perro Toby = new Perro(); Toby.descripción(); } } // Al ejecutar el programa veremos que se escribe el mensaje de // la clase hija, no el del padre. Modelos de Ejecución Mamifero carnivoro 137 Programas ejemplo. Clases abstractas. Sobrecarga de métodos /*Programa 1 - Versión 1*/ /* El concepto de polimorfismo, en cuanto a cambio de forma, se * puede extender a los métodos. * Java permite que varios métodos dentro de una clase se llamen * igual, siempre y cuando su lista de parámetros sea distinta *(en cuanto a número o tipo de argumentos). * / /** Diversos modos de sumar (versión estática)*/ public class Sumar { public static float suma(float a, float b) { System.out.println("suma de reales"); return a+b; } public static int suma(int a, int b) { System.out.println("suma de enteros"); return a+b; } public static void main(String[] args) { float x=1,y=2; int v = 3,w = 5; System.out.println(suma(x,y)); System.out.println(suma(v,w)); } } Modelos de Ejecución versión 1 suma de reales 3.0 suma de enteros 8 138 Programas ejemplo. Clases abstractas. /*Programa 1 - Versión 2*/ /** Diversos modos de sumar(versión flexible) */ public class Sumar { // método principal public static void main(String[] args) { try { // controla que el usuario ingrese correctamente los // valores float valor1=Float.parseFloat(args[0]); float valor2=Float.parseFloat(args[1]); int tipo = Integer.parseInt(args[2]); switch (tipo){ case 1: int x=(int)valor1; int y=(int)valor2; System.out.println(suma(x,y)); break; case 2: System.out.println(suma(valor1,valor2)); break; default: System.out.println("El 3er valor debe ser:"); System.out.println("1:si desea sumar enteros"); System.out.println("2:si desea sumar reales"); } } catch(ArrayIndexOutOfBoundsException e){ System.out.println("Por Favor ingrese 3 valores"); System.out.println("1er valor --> 1er sumando"); System.out.println("2do valor --> 2do sumando"); System.out.println("3er valor --> 1 para sumar enteros"); System.out.println(" --> 2 para sumar reales"); } } // método de clase(Static) que arroja valores REALES public static float suma(float a, float b) { System.out.println("suma de reales"); return a+b; } // método de clase(Static) que arroja valores ENTEROS public static int suma(int a, int b) { System.out.println("Estoy sumando enteros"); return a+b; } } 139 Programas ejemplo. Clases abstractas. Modelos de Ejecución versión 2 C:\ Por 1er 2do 3er JAVA Sumar Favor ingrese 3 valores valor --> 1er sumando valor --> 2do sumando valor --> 1 para sumar enteros --> 2 para sumar reales C:\JAVA Sumar 7 7 El 3er valor debe 1: si desea sumar 2: si desea sumar 7 ser: enteros reales C:\JAVA Sumar 7 7 1 Suma de enteros 14 C:\Sumar 7 7 2 Suma de reales 14.0 140 Programas ejemplo. Clases abstractas. /** Programa 2 */ /** Calcula el área de un rectángulo */ public class Rectangulo { private int largo,ancho; // variables privadas: únicamente se podrán utilizar en // esta clase // metodo principal donde comienza la ejecución del // programa public static void main(String[] args) { // Bloque TRY/CATCH controla que el usuario ingrese los // valores correctamente try{ int n1=Integer.parseInt(args[0]); // n1 toma el 1er valor ingresado por el usuario int n2=Integer.parseInt(args[1]); // n2 toma el 2do valor ingresado por el usuario // crea un objeto de la clase Rectángulo - se pasa // como argumento los valores n1 y n2 Rectángulo rec1 = new Rectángulo(n1,n2); // crea un objeto de la clase Rectángulo sin // argumentos Rectángulo rec2 = new Rectángulo(); System.out.println("Área Rectángulo Usuario"); // Llama implícitamente al método calcularArea(con // argumentos) System.out.println(rec1.calcularArea()); System.out.println("Área Rectángulo Computadora"); // Llama implícitamente al método calcularArea(sin // argumentos) System.out.println(rec2.calcularArea()); } catch(ArrayIndexOutOfBoundsException e){ System.out.println("Por Favor Ingrese los valores del largo y ancho del rectangulo"); } } // rec1 utiliza este Método Constructor pues se pasó 2 // argumentos durante su creación public Rectangulo(int lar, int anch) { largo = lar; // largo y ancho tendrán los valores ingresado por el // usuario ancho = anch; } 141 Programas ejemplo. Clases abstractas. // rec2 utiliza este Método Constructor pues no se pasó // argumento alguno durante su creación public Rectangulo() { // largo y ancho tendrán valores aleatorios ingresados por la // función random de la clase Math } largo = (int)(Math.random()*10); ancho = (int)(Math.random()*10); // método al que se llama con los objetos rec1 y rec2 en el // método main(principal) } public float calcularArea() { int area=(int)Math.abs(largo*ancho); return area; //e es el valor que se devuelve al programa llamador } Modelos de Ejecución C:\java Rectangulo Por Favor Ingrese los valores del largo y ancho del rectangulo C:\java Rectangulo 5 5 Area Rectangulo Usuario 25.0 Area Rectangulo Computadora 32.0 142 Programas ejemplo. Clases abstractas. /*Programa 3 */ /** Aproximación al Número E */ /** Este ejemplo utiliza los métodos de la clase PrintStream que * proporciona utilidades para dar formato a la salida. Dichos * métodos son print y println que están SOBRECARGADOS para los * tipos primitivos, cadenas y arrays de caracteres. * La diferencia entre ambos métodos está en que println añade un * carácter de nueva línea. Además el método println puede * llamarse sin argumentos, produciendo una nueva línea. */ class NumE { //clase PACKAGE (por defecto) public static void main (String args[]){ try{ // n contiene el nº ingresado por teclado con el cual // nos aproximaremos al nº E int n=Integer.parseInt(args[0]); double vector[]=new double [(n+1)]; int i=0, j=0, k=0; double numE=0; // Se controla que el usuario no ingrese valores // negativos o nulo if (n<=0) System.out.println("La formula solo permite aproximarse al nº e con valores positivos"); else { vector[0]=1;//0!=1 vector[1]=1;//1!=1 for(i=2; i<=n; i++){ j=i;k=i; while (j>1){ j--; k*=j; } //n! = n*(n-1)! -variable k //contendrá el valor k! vector[i]=k; } } // Forma el número aproximado a "e" for (i=0; i<=n; i++) { numE+=(1/(vector[i])); } // Salida por pantalla System.out.println(); System.out.println("Valor Aproximado: "+numE); System.out.println("Numero e: "+Math.E); System.out.println("Margen de error: "+(Math.EnumE)); } catch(ArrayIndexOutOfBoundsException e){ System.out.println("Por favor ingrese el valor 143 Programas ejemplo. Clases abstractas. } } } con el cual desea aproximarse al num E "); Explicación del Algoritmo Existe una forma de aproximarse al número e = 2.71828182.... mediante la siguiente fórmula: 1/0! + 1/1! + ............+1/N! , la cual tiende al número e cuando N tiende a infinito. Por lo tanto en este programa se define la clase NumE, el método constructor y las variables necesarias. Utilizamos un vector de n+1 elementos, recorriéndolo mediante un bucle For y lo cargamos con el factorial correspondiente a cada posición (pos 0 --> 0!, .pos N --> N! ). Posteriormente se calcula y se da salida al número aproximado obtenido, determinando además el margen de error existente (e - valor aproximado) Modelos de Ejecución C:\java NumE Por favor ingrese el valor con el cual desea aproximarse al num E C:\java NumE 23 Valor Aproximado: 2.7182818330591023 Numero e: 2.718281828459045 Margen de error: -4.600057224024567E-9 C:\java NumE 30 Valor Aproximado: 2.718281831563322 Numero e: 2.718281828459045 Margen de error: -3.104276835586006E-9 C:\java NumE 50 Valor Aproximado: Infinity Numero e: 2.718281828459045 Margen de error: -Infinity 144 Programas ejemplo. Clases abstractas. /*Programa 4 */ /** Este ejemplo compara un número ingresado por el usuario con * un valor aleatorio arrojado por la máquina determinando cual * es el mayor en términos de Enteros y Decimales * * Métodos Sobrecargados: * El Método "max" de la clase Math,se utiliza 2 veces con * argumentos de distintos tipos. * El Metodo "println" de la clase PrintStream, se utiliza varias * veces con distintos tipos y/o número de argumentos. */ public class Mayor{ public static void main(String arg[]){ try { // se controla que el usuario ingrese // correctamente los valores //"va" alberga el valor ingresado por el usuario double va=Double.parseDouble(arg[0]); // "vc" alberga un valor determinado // aleatoriamente por la función random de la // clase Math double vc=(Math.random()*100 int vb=(int)va; // se transfiere a la variable entera "vb" el // valor real de precisión doble de la var."va" } 145 } int vd=(int)vc; //salida por pantalla System.out.println(); System.out.println("Numeros Reales"); System.out.println("Usted ha ingresado el numero:" + va); System.out.println("La computadora ingreso el numero:"+ vc); System.out.println("El Mayor es:"+ Math.max(va,vc)); System.out.println(); System.out.println("Numeros Enteros"); System.out.println("Usted ha ingresado el numero:" + vb); System.out.println("La computadora ingreso el numero:"+ vd); System.out.println("El Mayor es:"+ Math.max(vb,vd)); }catch(IndexOutOfBoundsException e){ System.out.println("Por Favor Ingrese un Numero");} Programas ejemplo. Clases abstractas. Modelos de Ejecución C:\java Mayor Por Favor Ingrese un Numero C:\java Mayor 55 Numeros Reales Usted ha ingresado el numero:55.0 La computadora ingreso el numero:60.24044742541519 El Mayor es:60.24044742541519 Numeros Enteros Usted ha ingresado el numero:55 La computadora ingreso el numero:60 El Mayor es:60 C:\java Mayor 23 Numeros Reales Usted ha ingresado el numero:23.0 La computadora ingreso el numero:11.226574387976019 El Mayor es:23.0 Numeros Enteros Usted ha ingresado el numero:23 La computadora ingreso el numero:11 El Mayor es:23 146 Programas ejemplo. Clases abstractas. Clases abstractas /** Programa 1 */ /** Una Clase Abstracta (abstract) es una clase de la que no se * pueden crear objetos. Su utilidad es permitir que otras clases * deriven de ella, proporcionándoles un modelo a seguir y * algunos métodos de utilidad general. */ abstract class LenguajesDeProgramacion { String[] nom=new String[4]; public abstract void detalle(); // método abstract(no se da su definición) } class Declarativos extends LenguajesDeProgramacion { final String tipo="Funcionales"; public void detalle() { nom[0] = "lisp"; nom[1] = "Hope"; nom[2] = "Miranda"; nom[3] = "Haskell"; System.out.println("Lenguajes Declarativos"); System.out.println("\n"+tipo); for (int i=0;i<4;i++) System.out.println(nom[i]); } } public class Abstractos { public static void main(String[] args) { Declarativos nombre = new Declarativos(); nombre.detalle(); } } Modelos de Ejecución C:\ java Abstractos Lenguajes Declarativos Funcionales lisp Hope Miranda Haskell 147 Programas ejemplo. Clases abstractas. /**Programa 2 – versión 1*/ abstract class Vitamina { String alimento[]=new String[3]; public abstract void detalle(); } class A extends Vitamina{ public void detalle() { alimento[0]="vegetales";alimento[1]="Yema de Huevo"; alimento[2]="leche"; System.out.println("La vitamina A se encuentra en:"); for (int i=0;i<3;i++){ System.out.println(alimento[i]); } } } public class Medicina { public static void main(String[] args) { A vita = new A(); vita.detalle(); } } Modelos de Ejecución C:\ java Medicina La vitamina A se encuentra en: vegetales Yema de Huevo Leche 148 Programas ejemplo. Clases abstractas. /**Programa 2 – versión 2*/ abstract class Vitamina { String alimento[]=new String[3]; public abstract void detalle(); } class A extends Vitamina{ public void detalle() { alimento[0]="vegetales";alimento[1]="Yema de Huevo"; alimento[2]="leche"; System.out.println("La vitamina A se encuentra en:"); for (int i=0;i<3;i++){ System.out.println(alimento[i]); } } } class E extends Vitamina{ public void detalle() { alimento[0]="germen de trigo"; alimento[1]="aceite de soja"; alimento[2]="maiz"; System.out.println("\n"+"La vitamina E se encuentra en:"); for (int i=0;i<3;i++){ System.out.println(alimento[i]); } } } public class Medicina1 { public static void main(String[] args) { A vita = new A();vita.detalle(); E vita1 = new E();vita1.detalle(); } } Modelos de Ejecución C:\java Medicina1 La vitamina A se encuentra en: vegetales Yema de Huevo Leche La vitamina E se encuentra en: germen de trigo aceite de soja maiz 149 Programas ejemplo. Polimorfismo. Polimorfismo /*Programa 1 – versión1 */ /** En muchas ocasiones, cuando utilizamos herencia podemos * terminar teniendo una familia de clases que comparten un * interfaz común. */ //clase PADRE abstract class Lenguajes { String nom[]=new String[3]; // se declara un vector "nom[]" de la clase String y se lo // inicializa con 3 posiciones } public abstract void detallar(); //clase HIJA (hereda la variable y método de la clase PADRE) class Objetos extends Lenguajes { public void detallar() { // se redefine como public el método detallar // (definido como abstract) nom[0]="C++"; nom[1]="Java"; nom[2]="Eiffell"; System.out.println("Lenguajes de Prog.Orientados a Objetos"); for(int i=0;i<3;i++){ System.out.println(nom[i]); } } } //clase HIJA class Declarativos extends Lenguajes { public void detallar() { // se redefine como public el método detallar // (definido como abstract) nom[0]="Lisp"; nom[1]="Prolog"; nom[2]="Haskell"; System.out.println("\n"); System.out.println("Lenguajes de Prog.Declarativos"); for(int i=0;i<3;i++){ System.out.println(nom[i]); } } } public class Informatica1 { public static void detalle(Lenguajes m) { 151 Programas ejemplo. Polimorfismo. // el método detalle utiliza un objeto "m" de la // clase Lenguajes } m.detallar(); // se llama al método detallar con un argumento // Implícito objeto.método // método PRINCIPAL public static void main(String[] args) { Objetos leng1 = new Objetos(); // crea un objeto "leng1" de la clase Objetos Declarativos leng2= new Declarativos(); // crea un objeto "leng2" de la clase Declarativos detalle(leng1); // llama al método detalle pasando el objeto "leng1" // como argumento explícito } } detalle(leng2); // llama al método detalle pasando el objeto "leng2" // como argumento explícito /*el método detalle llama al método detallar ejecutándose las * sentencias que componen este último método para el objeto * especifico que lo llama.*(primero llama a un leng. Orientado a Objetos y luego a un * leng. Declarativo) */ Modelos de Ejecución Lenguajes de Prog.Orientados a Objetos C++ Java Eiffell Lenguajes de Prog.Declarativos Lisp Prolog Haskell 152 Programas ejemplo. Polimorfismo. /*Programa 2 – versión 1 */ /** CALCULA AREAS DE DISTINTAS FIGURAS (versión estática)*/ abstract class Figura{ abstract public double area(); } //clase abstracta class Circulo extends Figura{ //clase derivada de Figura private double radio; // var private solo podrá ser utilizada por la clase // Círculo } final double pi=3.1416; // var final: (constante) no puede cambiar su valor durante // la ejecución del prog. // método constructor de la clase Circulo utiliza un // argumento pasado por referencia desde el método main // (principal) Circulo(double r){ radio=r; } // redefinición del método area(), public double area(){ return pi*radio*radio; // valor que retorna al programa llamador } class Cuadrado extends Figura{ private double lado; Cuadrado(double l){ lado=l; } public double area(){ return lado*lado; } } class Triangulo extends Figura{ private double base; private double altura; Triangulo(double b, double a){ base=b; altura=a; } public double area(){ return 0.5*base*altura; } } class polimorfismo{ // constructor principal (donde comienza la ejecución del // programa) 153 Programas ejemplo. Polimorfismo. public static void main(String args[]){ // control de Excepciones producidas por el usuario try{ double n1=Double.parseDouble(args[0]); double n2=Double.parseDouble(args[1]); double ñ=Double.parseDouble(args[2]); double m=Double.parseDouble(args[3]); Triangulo tri=new Triangulo(n1,n2); // se crea un objeto de la clase "Triangulo" Circulo cir=new Circulo(m); Cuadrado cua=new Cuadrado(ñ); // salida por pantalla a la información obtenida // en la ejecución del programa n2); System.out.println(); System.out.println("TRIANGULO"); System.out.println("base: "+n1 + " altura: "+ System.out.println("Area "+tri.area()); System.out.println(); System.out.println("CUADRADO"); System.out.println("lado: "+ñ); System.out.println("Area: "+cua.area()); System.out.println(); System.out.println("CIRCULO"); System.out.println("radio: "+m); System.out.println("Area "+cir.area()); System.out.println(); } } }catch(ArrayIndexOutOfBoundsException e){ System.out.println("Por Favor,ingrese 4 valores"); System.out.println("\n"+ "1er valor: Base Triangulo"); System.out.println("\n"+ "2do valor: Altura Triangulo"); System.out.println("\n"+ "3er valor: Lado Cuadrado"); System.out.println("\n"+ "4to valor: Radio Circulo"); } 154 Programas ejemplo. Polimorfismo. Modelos de Ejecución C:\java polimorfismo Por Favor, ingrese 4 valores 1er valor: Base Triangulo 2do valor: Altura Triangulo 3er valor: Lado Cuadrado 4to valor: Radio Circulo C:\java polimorfismo 7 5 8 2 TRIANGULO base: 7.0 Area 17.5 altura: 5.0 CUADRADO lado: 8.0 Area: 64.0 CIRCULO radio: 2.0 Area 12.5664 155 Programas ejemplo. Polimorfismo. /**Programa 2 – versión 2 */ /**CALCULAR AREA DE DISTINTAS FIGURAS (versión flexible)*/ abstract class Figura{ abstract public double area(); } // clase abstracta class Circulo extends Figura{ private double radio; final double pi=3.1416; // método constructor de la clase Circulo // utiliza un argumento pasado por referencia desde el // método main(principal) Circulo(double r){ radio=r; } // redefinición del método area(), se declara como publico // para que pueda ser accedido por todas las clases del // paquete } public double area(){ return pi*radio*radio; //valor que retorna al programa llamador } class Cuadrado extends Figura{ private double lado; Cuadrado(double l){ lado=l; } public double area(){ return lado*lado; } } class Triangulo extends Figura{ private double base; private double altura; Triangulo(double b, double a){ base=b; altura=a; } public double area(){ return 0.5*base*altura; } } 156 Programas ejemplo. Polimorfismo. class Polimorfismo{ // constructor principal (donde comienza la ejecución del // programa) public static void main(String args[]){ // control de Excepciones producidas por el usuario try{ double n1=Double.parseDouble(args[1]); // n1= 2º valor ingresado por el usuario int picture=Integer.parseInt(args[0]); //picture = 1º valor ingresado por el usuario switch(picture){ case 1: Circulo cir=new Circulo(n1); System.out.println("\n"+"CIRCULO"); System.out.println("radio: "+n1); System.out.println("Area "+cir.area()); System.out.println("\n"+"Desea calcular el area de otra figura?"); System.out.println("\n"+"Para calcular area de un: "); System.out.println("CIRCULO: ingrese 1 y el valor que desea darle al Radio"); System.out.println("CUADRADO: ingrese 2 y el valor que desea darle al Lado"); System.out.println("TRIANGULO: ingrese 3 y los valores que desea darle a la Base y a la Altura"); break; case 2: Cuadrado cua=new Cuadrado(n1); System.out.println("CUADRADO "); System.out.println("lado: "+n1); System.out.println("Area: "+cua.area()); System.out.println("\n"+"Desea calcular el area de otra figura?"); System.out.println("\n"+"Para calcular area de un: "); System.out.println("CIRCULO: ingrese 1 y el valor que desea darle al Radio"); System.out.println("CUADRADO: ingrese 2 y el valor que desea darle al Lado"); System.out.println("TRIANGULO: ingrese 3 y los valores que desea darle a la Base y a la Altura"); break; 157 Programas ejemplo. Polimorfismo. case 3: double n2=Double.parseDouble(args[2]); Triangulo tri=new Triangulo(n1,n2); System.out.println("TRIANGULO"); System.out.println("base: "+n1 + " altura: "+ n2); System.out.println("Area "+tri.area()); System.out.println("\n"+"Desea calcular el area de otra figura?"); System.out.println("\n"+"Para calcular area de un: "); System.out.println("CIRCULO: ingrese 1 y el valor que desea darle al Radio"); System.out.println("CUADRADO: ingrese 2 y el valor que desea darle al Lado"); System.out.println("TRIANGULO: ingrese 3 y los valores que desea darle a la Base y a la Altura"); break; } }catch(ArrayIndexOutOfBoundsException e){ System.out.println("\n"+"Ingrese 2 valores, si desea calcular el area de:"); System.out.println("\n"+"un CIRCULO"); System.out.println("1er valor: 1 " + "\t" + "2do valor: RADIO CIRCULO"); System.out.println("\n"+"un CUADRADO"); System.out.println("1er valor: 2 " + "\t" + "2do valor: LADO CUADRADO"); System.out.println(); } } } System.out.println("\n"+"Ingrese 3 valores, si desea calcular el area de:"); System.out.println("\n"+"un TRIANGULO"); System.out.println("1er valor: 3 " + "\t" + "2do valor: BASE TRIANGULO; 3er valor: ALTURA TRIANGULO"); /** Este programa interactúa con el usuario brindádole la * posibilidad de elegir de que figura desea calcular el área */ 158 Programas ejemplo. Polimorfismo. Modelos de Ejecución C:\java Polimorfismo Ingrese 2 valores, si desea calcular el area de: un CIRCULO 1er valor: 1 2do valor: RADIO CIRCULO un CUADRADO 1er valor: 2 2do valor: LADO CUADRADO Ingrese 3 valores, si desea calcular el area de: un TRIANGULO 1er valor: 3 2do valor: BASE TRIANGULO; 3er valor: ALTURA TRIANGULO C:\java Polimorfismo 1 10 CIRCULO radio: 10.0 Area 314.16 Desea calcular el area de otra figura? Para calcular area de un: CIRCULO: ingrese 1 y el valor que desea darle al Radio CUADRADO: ingrese 2 y el valor que desea darle al Lado TRIANGULO: ingrese 3 y los valores que desea darle a la Base y a la Altura C:\java Polimorfismo 2 10 CUADRADO lado: 10.0 Area: 100.0 Desea calcular el area de otra figura? Para calcular area de un: CIRCULO: ingrese 1 y el valor que desea darle al Radio CUADRADO: ingrese 2 y el valor que desea darle al Lado TRIANGULO: ingrese 3 y los valores que desea darle a la Base y a la Altura 159 Programas ejemplo. Polimorfismo. /**Programa 3 */ /**subprograma 1*/ package polimorfismo; public class Polimorfis { public static void main(String[] args) { Espinacas espi=new Espinacas(); que_aporta(espi); Tomate tomate=new Tomate(); que_aporta(tomate); try { } } //espera la pulsación de una tecla y luego RETORNO System.in.read(); }catch (Exception e) { } static void que_aporta(Vitaminas alimento){ alimento.aporte(); } /** subprograma 2*/ package polimorfismo; public abstract class Frutas { } class Citricos extends Frutas implements Vitaminas{ public void aporte(){ System.out.println("Los cítricos aportan vitamina C"); } } /** subprograma 3*/ package polimorfismo; // clase Base implementa la interfaz Vitaminas public abstract class Verduras implements Vitaminas{ public abstract void aporte(); //define un método abstracto } // clase derivada de Verduras class Espinacas extends Verduras{ public void aporte(){ System.out.println("Las Espinacas aportan vitamina B9"); } } 160 Programas ejemplo. Polimorfismo. // clase derivada de Verduras class Tomate extends Verduras{ public void aporte(){ System.out.println("El tomate aporta vitamina k"); } } /** subprograma 4*/ package polimorfismo; public interface Vitaminas { public abstract void aporte(); } /* En el lenguaje Java solamente existe la herencia simple, pero * las clases pueden implementar interfases, las cuales * incrementan el polimorfismo del lenguaje más allá del que * proporciona la herencia simple. * Si solamente hubiese herencia simple, Citricos tendría que * derivar de la clase Verduras (lo que no es lógico) o bien no * se podría pasar a la función que_aporta. * Con interfases, cualquier clase en cualquier familia puede * implementar la interfase Vitaminas, y se podrá pasar un objeto * de dicha clase a la función "que_aporta". * Esta es la razón por la cual las interfases proporcionan más * polimorfismo que el que se puede obtener de una simple * jerarquía de clases. */ Modelos de Ejecución Las Espinacas aportan vitamina B9 El tomate aporta vitamina k 161 Programas ejemplo. Ligaduras Dinámicas. Ligaduras dinámicas /** Programa 1 */ /** Subprograma 1*/ package Figura; public abstract class Figura { public abstract double area(); } class Circulo extends Figura{ protected double radio; public Circulo(double radio){ this.radio = radio; // variable miembro radio alberga el valor del // argumento radio } public double area(){ //redefine el método abstracto de la clase Base return Math.PI*radio*radio; //retorna el valor del área del círculo } } class Cuadrado extends Figura{ protected double lado; public Cuadrado(double lado){ this.lado=lado; } public double area(){ return lado*lado; } } /* variables PROTECTED: sólo la clase en la que se define, las * clases que deriven de ella y las clases del propio package * tienen permiso para utilizarlas. * JAVA permite declarar una variable dentro de un bloque con el * mismo nombre que una variable miembro. * La variable declarada dentro del bloque oculta a la variable * miembro en ese bloque. * Para acceder a la variable miembro oculta será preciso * utilizar el operador this, en la forma this.varname. * De esta forma, this.radio se refiere a la variable miembro, * mientras que radio es el argumento del constructor. */ 163 Programas ejemplo. Ligaduras Dinámicas. /**Subprograma 2*/ package Figura; public class FiguraApp { public static void main(String[] args) { // enlace temprano: tiempo de compilación Circulo c=new Circulo(5.5); System.out.println("Area del circulo "+ c.area()); Cuadrado r=new Cuadrado(5.5); System.out.println("Area del cuadrado "+r.area()); // enlace tardío 1: tiempo de ejecución // valores predefinidos Figura f=new Circulo(2); System.out.println("Area del circulo "+f.area()); f=new Cuadrado(3); System.out.println("Area del cuadrado "+f.area()); // enlace tardío 2 // valores ingresados por teclado try { Figura fig[]=new Figura[2]; int variable=System.in.read(); // toma el entero correspondiente a alt+variable } } System.out.println("las areas se calcularan con el valor: "+variable); fig[0]=new Cuadrado(variable); fig[1]=new Circulo(variable); for(int p=0;p<2;p++) System.out.println(fig[p].area()); }catch (Exception e) {} Modelos de Ejecución C:\ java Area del Area del Area del Area del FiguraApp circulo 95.03317777109123 cuadrado 30.25 circulo 12.566370614359172 cuadrado 9.0 164