Orientación a objetos con Java y UML

102
Desarrollo orientado a objetos con UML Aspecto de la captura de requisitos y el análisis Captura de requisitos Tiene lugar como primera tarea en el desarrollo de un sistema en el cual mediante entrevistas con clientes y usuarios se establece lo que se desea del software a desarrollar. El analista le da ideas al usuario sobre qué cosas son posibles (según el costo pactado) en el desarrollo del software. Requisitos funcionales: procesos del negocio que debe hacer el sistema. Requisitos operativos: tienen que ver con la escalabilidad, disponibilidad, seguridad, facilidad de mantenimiento, desempeño, etc. Casos de uso y diagramas de casos de uso El caso de uso es añadido a la documentación de requisitos. Es una interacción entre usuarios y un sistema. Ej. de diagrama de casos de uso para la administración de catálogos de una biblioteca. 1

Transcript of Orientación a objetos con Java y UML

Desarrollo orientado a objetos con UML

Aspecto de la captura de requisitos y el análisis

Captura de requisitos

Tiene lugar como primera tarea en el desarrollo de un sistema en el cual mediante entrevistas con clientes y usuarios se establece lo que se desea del software a desarrollar. El analista le da ideas al usuario sobre qué cosas son posibles (según el costo pactado) en el desarrollo del software.

Requisitos funcionales: procesos del negocio que debe hacer el sistema.Requisitos operativos: tienen que ver con la escalabilidad, disponibilidad, seguridad, facilidad de mantenimiento, desempeño, etc.

Casos de uso y diagramas de casos de uso

El caso de uso es añadido a la documentación de requisitos. Es una interacción entre usuarios y un sistema.

Ej. de diagrama de casos de uso para la administración de catálogos de una biblioteca.

1

Los actores son personas, los casos de uso son elipses y las comunicaciones son líneas. Los actores pueden ser cualquier entidad externa, incluso otros sistemas, y una misma persona puede actuar en varios roles a la vez.

Análisis

El análisis consiste en convertir los requerimientos en abstracciones más cercanas al programa a construir. Esto lo hace difícil de distinguir del diseño, pero en realidad la principal diferencia consiste en que el análisis es independiente de cualquier cuestión tecnológica o implementación, mientras que el diseño no. Así mientras en el análisis hay que encontrar clases y objetos, en el diseño se piensa en las abstracciones y mecanismos que sirvan para soportar el comportamiento.Hay dos aspectos que deben determinarse en el análisis:

1. La estructura de los objetos del espacio del problema: cómo se relacionan los objetos, cómo se organizan en tipos, jerarquía de los tipos, análisis de composición de objetos.

2. El comportamiento de los objetos del espacio del problema: en qué estados puede estar cada objeto, con qué transiciones y eventos cambia de estado, las operaciones a las que responde y su semántica.

El diagrama de actividades de UML

El diagrama de actividades resalta el flujo de control entre objetos. Suele ser útil para ver el flujo de información.

Diagrama de actividades de la extracción de dinero de un cajero automático.

2

La separación de tareas en paralelo se hace mediante una barra gruesa, y lo mismo ocurre con su unión posterior. Un rombo permite denotar ramificaciones basadas en alguna condición.

Se pueden usar para modelar:

Flujos de procesos. Especificación de comportamiento de alto nivel. Especificación de algoritmos. Actividades concurrentes. Secuencialidades innecesarias en procesos de negocio. Distintos agentes u objetos en un flujo.

3

Las interacciones y comportamientos que abarcan varios casos de uso, posiblemente concurrentes.

Casos de prueba.

Modelo de casos de uso

El modelo de casos de uso es una descripción de todos los casos de uso relevados en la captura de requisitos. Se usa para:

Expresar descripciones de la funcionalidad independientes de la implementación.

Poner énfasis en lo que el sistema debe hacer más que en cómo lo va a resolver.

Particionar el conjunto de necesidades de los usuarios. Servir como testigos durante todo el ciclo de vida, desde el análisis hasta las

pruebas.

A continuación se muestra una descripción de un caso de uso:

4

Un diagrama de colaboración es, como el diagrama de secuencias, otro diagrama de interacción.

A continuación se muestra un diagrama de colaboración, que corresponde al diagrama de secuencia de una interacción con un cajero automático:

5

Los diagramas de interacción (secuencia o colaboración) se usan para modelar:

Cómo un escenario puede ser realizado a través de un conjunto de mensajes entre objetos.

Las operaciones (métodos) de las clases. Distintos objetos trabajando en conjunto. Mensajes asíncronos.

El diagrama de colaboración enfatiza relaciones estructurales entre objetos y es una herramienta útil para encontrar objetos. Muestra tanto la estructura estática (enlaces) como la dinámica (envío de mensajes). Sirve para ver las conexiones entre objetos, como vínculos conceptuales o de dependencia. Objetos e interacciones, pero privilegiando las relaciones entre los distintos objetos.El diagrama de secuencia enfatiza el ordenamiento temporal y el ciclo de vida de los objetos. Representación temporal de los objetos y sus interacciones.

6

Hallando las clases de análisis

Clases en UML:Estructura estática en términos de clases, interfaces y

colaboraciones, y las relaciones entre ellas.

Paquetes en UML:Grupos de clases y sus dependencias.

Objetos en UML:Instancias de los elementos de un diagrama de clases,

mostrando un conjunto de objetos en un momento concreto, con sus estados y relaciones.

Las clases de análisis surgen directamente de los requerimientos. Por ejemplo, si se está diseñando un software que juegue al ajedrez, la clase pieza va a ser inmediata.En contraposición a las metodologías estructuradas, la idea no es preguntarse qué hace el sistema sino a qué o a quién se lo hace. De esa forma se encontrarán los objetos y clases.En las metodologías orientadas a objetos se buscan las clases a partir de los objetos de los casos de uso.Para encontrar objetos lo que se debe hacer es tomar cada elemento conceptual en el problema y presentarlo como un objeto.Cuando una clase esté brindando demasiados servicios se deberá separar en dos o más clases.El paso siguiente es encontrar las colaboraciones. Una colaboración denota una sociedad de clases, interfaces y otros elementos, que cooperan para proporcionar un comportamiento corporativo mayor que la suma de los comportamientos de sus partes.La mejor forma de representar una colaboración es mediante un diagrama de clases.

Tarjetas CRC

Las tarjetas CRC (clases, responsabilidades y colaboradores) son una herramienta muy útil para la búsqueda de las clases de análisis.

Responsabilidad: es todo lo que una clase conoce o hace. De una sola responsabilidad podrán surgir varios métodos. Para encontrar responsabilidades, así como para las clases se usan los sustantivos de los documentos de requerimientos, podemos utilizar los verbos.

7

Colaborador: es una clase que provee información a la clase en cuestión o realiza alguna acción en respuesta a un mensaje de la misma.

Las tarjetas CRC permiten determinar una especie de contrato u obligación de la clase.

La idea consiste en describir cada clase en tarjetas de no más de 10x15 cm67, en la cual se escribe el nombre de la clase, sus responsabilidades y colaboradores.

Ejemplo:

Si alguna de estas clases es un contenedor, las clases de los objetos contenidos deben figurar entre las colaboraciones y deberán aparecer en otras tarjetas.Si en algún momento no alcanza la tarjeta para describir las colaboraciones o las responsabilidades de una clase, habrá que preguntarse si la misma no debería ser separada en dos clases más simples.

Diagrama de clases con responsabilidades

Los creadores de UML han introducido las responsabilidades en el diagrama de clases.

Diagrama del patrón MVC mediante un diagrama de clases de análisis con responsabilidades:

8

Diseño de arquitecturas

El diseño como puente entre el análisis y la implementación

Diseño

Es el “cómo” del desarrollo. Es la fase más ingenieril del desarrollo del software, pese a llamarse arquitectura. Se aplica para concebir una solución a un problema, creando una estructura interna clara y lo más simple posible.

Mínimamente, como resultado del diseño deberíamos poder exhibir:

Las clases de diseño y las de implementación, con su estructura, operaciones y semántica.

Las distintas arquitecturas del sistema. Otras decisiones de diseño.

Las clases de diseño son aquellas que, si bien describen conceptos de alto nivel, como las clases de análisis, no pertenecen al dominio del problema sino al de la solución.Las clases de implementación son las que surgen por necesidades algorítmicas, como un tipo de arreglo, lista o fecha definidos por el programador.

Entre las decisiones de diseño que no corresponden a arquitecturas ni al diseño de clases están:

9

Definiciones sobre implementación: explicita los lenguajes, herramientas y nomenclatura que se van a utilizar en la programación, además de la plataforma de software de base.

Definiciones de interfaz de usuario: precisa el tipo de interfaz de usuario, sus elementos principales en cada caso, relaciones entre ellos y normas de usabilidad.

Arquitecturas

Las arquitecturas son formas de descomponer, conectar y relacionar partes de un sistema. Pueden ser de diversos tipos:

Arquitectura de componentes, en general llamada simplemente arquitectura, es una visión de alto nivel del sistema que consiste en una descripción de sus principales componentes de software y sus interacciones (se habla de arquitectura MVC, arquitectura de 3 capas, etc.). Suele estar basada en patrones de diseño.

Arquitectura de hardware: define el despliegue de los distintos componentes en los nodos de hardware. En ocasiones incluye la arquitectura de seguridad (firewalls, proxies, etc.).

Arquitectura de conectividad: se refiere a los protocolos y dispositivos a usar.

Arquitectura de datos: estructura de las bases de datos y la persistencia.

Ejemplo de patrón de arquitectura I: MVC

Separación de la interfaz de usuario

Una forma de ver las partes de un sistema, sobre todo cuando la interacción con el usuario es muy importante, es dividiéndolas en:

La componente de diálogo o interfaz de usuario, que es el software que soporta y permite que el diálogo hombre-máquina se lleve a cabo.

La componente de cómputos, que permite la transformación funcional o algorítmica de las entradas en salidas.

En cualquiera de los dos modelos vemos la idea del diálogo o interfaz de usuarios, también llamado interacción humano-computadora (o HCI, por su sigla en inglés), que es el intercambio observable de información, datos y acciones, mediante los cuales el ser humano dice a la computadora lo que quiere y la computadora comunica resultados.

10

HCI es además una disciplina que se encarga del diseño, evaluación e implementación de sistemas informáticos interactivos para el uso humano. Esta disciplina ha adquirido una especial importancia por una serie de factores, entre los cuales destacan:

La interfaz de usuario se convirtió en un factor decisivo en la elección del software, de modo tal que hay un alto índice de software desechado por proveer una interfaz de usuario de baja calidad.

Inclusión de la computación en todos los ambientes e integración de la informática a otras áreas.

Menor costo en hardware, que ha provocado un mayor avance tecnológico relativo de la interacción humano-máquina.

El auge de Internet y la World Wide Web, con su correlato de aumento de la heterogeneidad en el espectro de usuarios.

Si tomamos la definición de un programa como diálogo más cómputo, podemos ver que estas dos partes difieren bastante, como muestra el cuadro que sigue:

Interfaz de usuario o componente de diálogo Modelo o componente de cómputoDependiente de la tecnología Independiente de la tecnologíaMuy cambiante y sujeto a modas pasajeras Más estable

Estas diferencias hacen que se pueda separar la interfaz del usuario del sistema de aplicación desde el inicio del desarrollo. Lo que se buscaba era:

Presentación y control dinámicos de la información. Múltiples formatos de salida (por ejemplo, a un monitor, una base de datos o

un archivo XML). Múltiples forma de mostrar la misma información (diagramas de torta y de

barras, por ejemplo). Múltiples tipos de estímulos del usuario (por ejemplo, teclado y mouse).

De esta forma se buscaba lograr ciertos objetivos, entre los que destacan:

Modificabilidad. Simplicidad. Escalabilidad. Independencia de la tecnología. Posibilitar el cambio de modelo sin modificar la interfaz de usuario. Posibilitar la evolución de la interfaz de usuario sin cambiar el modelo. Desarrollar múltiples interfaces de usuario para un mismo modelo.

11

Poder separar físicamente la lógica de una aplicación distribuida, que reside en una computadora remota, de los resultados mostrados en máquinas clientes.

Con base en estos requisitos, hubo varias propuestas destinadas a separar los componentes de diálogo y cómputo, destacándose la arquitectura MVC.

MVC

MVC es un patrón de arquitectura orientado a objetos que consiste en separar la aplicación en tres partes relativamente independientes: modelo, vista y controlador. Ha demostrado ser muy adecuado en aplicaciones con alta interacción con el usuario. Se basa en el patrón de diseño Observador. Es bueno en aplicaciones de mucha IU. Se suele combinar con otros. Facilita cambios. Muchas vistas por cada modelo.

El modelo es el conjunto de clases y objetos correspondientes a la lógica de la aplicación (estados y funcionalidad). La idea es que este modelo tenga un bajo acoplamiento con vistas y controladores. Toda la interacción se debe hacer mediante métodos de consulta, que informen el estado del modelo, comandos que permitan modificar dicho estado y mecanismos de notificación para informar a los observadores o vistas.La vista es la parte del sistema que administra la visualización y presentación de la información. Su principal tarea es observar al modelo para actualizar las variaciones, de modo que la vista represente el estado del modelo, así como los cambios de estado que experimenta el mismo. Es un conjunto de clases y objetos altamente dependientes del dispositivo y la tecnología de visualización, así como también del modelo, al que debe conocer bien. Si en el modelo se define una interfaz clara y estable, es fácil implementar múltiples vistas para un mismo modelo.El controlador es el responsable de definir el comportamiento de la aplicación. Su principal tarea consiste en recibir los eventos del usuario y decidir qué es lo que se debe hacer, mapeándolos en comandos (mensajes) hacia el modelo, que eventualmente lo modifican. Como la vista, igualmente el controlador es altamente dependiente de los dispositivos y mecanismos de interacción del usuario, y también debe conocer bien al modelo.

En un sistema con interfaz de usuario gráfica, una implementación simple de MVC consiste en un ciclo con la siguiente secuencia:

El controlador captura los eventos del usuario para determinar qué objetos están siendo manipulados.

12

Una vez determinado lo que está ocurriendo, envía mensajes al modelo para generar cambios de estado.

El modelo implementa las transformaciones y notifica a la vista. Finalmente, la vista se actualiza para reflejar las novedades.

Mecanismos de notificación por los cuales el modelo puede notificar sus cambios a las vistas:

Consulta de estado y respuesta: En este mecanismo, la vista consulta al modelo sobre su estado y se actualiza en consecuencia. El modelo desconoce qué pasa, limitándose a responder a los mensajes recibidos (comandos y consultas). Es la variante más en consonancia con la POO. Sin embargo, exige a la vista un mayor conocimiento del modelo que en las variantes que siguen.

Mediante eventos. Esta es una variante totalmente desacoplada, en la que las vistas escuchan y responden a los eventos de notificación de sus respectivos modelos, en la medida en que les interesa. El bajo acoplamiento se garantiza haciendo que las propias vistas sean quienes se suscriben en la lista del modelo, permitiendo un manejo totalmente dinámico.

Con observadores o vistas asociadas, conocidos por el modelo. Este les envía activamente un mensaje de notificación, sin información, para que luego las vistas se preocupen de actualizarse pidiendo datos al modelo. Este es un método fácil de implementar, y bastante flexible si utiliza eventos también.

Los tres mecanismos admiten trabajar con múltiples vistas.

La vista y el controlador están muy interrelacionados, pues ambos dependen de la interacción con el usuario. Sin embargo, no es así en sistemas web distribuidos, donde la funcionalidad del controlador en parte la maneja el generador de páginas en el servidor (para ello existe un patrón de diseño del controlador denominado controlador de página).

Consecuencias de la independencia de diálogo

La independencia de diálogo nos permite separar el modelo de su presentación. Este paradigma lo podemos usar para:

Implementar un objeto con múltiples vistas. Mostrar un documento en muchos idiomas. Ver un sitio web en diversas interfaces de usuario. Manejar una colección con varios medios de acceso. Etc.

13

Ejemplo de patrón de arquitectura II: aplicaciones en capas

El modelo de tres capas

La arquitectura de tres capas implica dividir el sistema en:

Capa de interfaz de usuario Lógica de la aplicación Capa de acceso a datos

El término capa se usa para indicar que una superior es dependiente de la inferior, y sólo se comunica con ella. En el modelo de tres capas, la interfaz de usuario es la capa superior y muestra lo que ocurre en la capa de la lógica de la aplicación (depende de lo que ocurre en ella). Asimismo esta última capa depende de la capa de acceso a datos, en la medida que se comunica con ella cada vez que necesita algo y lo obtiene de ella.

Las ventajas de este modelo son parecidas a las de MVC, pero más generales:

Posibilitar el cambio de modelo sin modificar la interfaz de usuario o el acceso a los datos.

Posibilitar la evolución de la interfaz de usuario sin cambiar el modelo o la base de datos.

14

Desarrollar múltiples interfaces de usuario o módulos de acceso a datos para un mismo modelo.

Posibilidad de utilizar diferentes herramientas y plataformas en cada capa. Posibilidad de utilizar diferentes paradigmas en capas distintas (por ejemplo,

usar orientación a objetos para el modelo con una base de datos relacional, como veremos en el capítulo de persistencia).

Cada capa puede ser desarrollada por un equipo distinto, con cronogramas relativamente independientes.

Herramientas de UML para modelizar arquitecturas

Diagramas de componentes

Simplemente arquitectura.Descripción de componentes de software y sus interacciones.Separación garantizada posibilidad de cambios.Generalmente basada en patrones: MVC, 3 capas, N capas.Combinables con las anteriores: por lo tanto, independientes.

El diagrama de componentes muestra componentes de software y sus dependencias. El término componente es tan ambiguo como se quiera, y puede representar bibliotecas de enlace dinámico, programas fuente, applets Java, etc. En general, un componente se corresponde con una clase o paquete, pero mientras un paquete o clase son conceptos lógicos, los componentes tienen existencia real.

Diagrama de componentes:

15

La representación de componentes software se hace con rectángulos con bisagras, que a menudo implementan una interfaz, representada con un círculo pequeño. Las dependencias se representan con flechas discontinuas (dependencia significa que un componente usa servicios de otro).

Componentes como piezas de software

Un componente es una parte física y reemplazable de un sistema de software que conforma con un conjunto de interfaces y proporciona la realización de dichas interfaces.

Diagramas de despliegue

Cómo se despliega un sistema en el hardware.Aplicaciones stand-alone: el sistema está desplegado enteramente en la máquina del usuario.Aplicaciones distribuidas: el usuario accede a un sistema corriendo parcialmente en una máquina remota. Las máquinas remotas pueden ser varias.Ventajas: Especialización de equipos; seguridad, en algunos casos; si es una web: más sencillos despliegue y actualización.

Los diagramas de emplazamiento o despliegue se usan para modelar el despliegue de los distintos componentes de software sobre el hardware y sus vinculaciones. De todas maneras, sólo son necesarios en sistemas en los que el

16

hardware sea importante. Los nodos de procesamiento y los componentes que residen en ellos.

Diseño de clases

Buenas prácticas de diseño

Encontrando clases de diseño e implementación

17

Bases del diseño de clases:

Una buena arquitectura. Buenas prácticas de diseño y programación. Patrones de diseño.

“Un diseño termina cuando no se pueden extraer más cosas del mismo” – Eckel.

Cada clase con un propósito simple y claro: una clase por abstracción y una abstracción por clase.Separar las dependencias de una plataforma en una clase aparte.

Patologías en diseño de clases:

Clases con nombres verbales: No se supone que una clase hace algo, sino que provee un conjunto de servicios.

Clases sin métodos. Clases que no introducen nuevos métodos ni los redefinen. Sólo heredan. Clases que se refieren a varias abstracciones: se deberían dividir en varias.

A veces hay atributos y comportamientos propios de las asociaciones, y en esos casos conviene crear clases para las mismas, llamadas clases de asociación:

Cohesión y acoplamiento

En la programación modular existen dos características que hacen a la calidad de los módulos: la cohesión, que debe ser lo más alta posible, y el acoplamiento, que debe ser todo lo bajo que se pueda. Esto facilitará el mantenimiento futuro.

La cohesión se refiere a la necesidad de que un módulo haga una sola cosa simple. Los módulos de un sistema exhiben alta cohesión.

18

El acoplamiento se refiere a la interdependencia entre módulos: un bajo acoplamiento garantiza que los módulos del sistema son relativamente independientes.

Asegurar bajo acoplamiento y alta cohesión en métodos, clases y paquetes.

Diseño de interfaces de clases

La interfaz de una clase es lo que ve el cliente. Más clara, consistente, simple, intuitiva. No quitar funcionalidad (uso de /**@deprecated*/ en Java. Privatizar lo más posible. Sólo si es necesario, publicarlas). Pocos parámetros.

Todo diseño de una clase debe comenzar con una interfaz mínima. Si bien luego vamos a poder ampliarla en versiones futuras, nunca vamos a poder suprimir métodos de una clase o biblioteca.

Otra forma de asegurar que no se van a afectar clientes es declarar atributos y métodos lo más privados que sea factible. Al fin y al cabo, los atributos y métodos privados no van a ser usados por ningún cliente y vamos a poder modificarlos más adelante.

Los atributos deberían mostrar sólo estado y los métodos sólo comportamiento.No abusar de la herencia para expresar estado.

Si en algún momento se necesita otra versión de un método que ya está en uso y se quiere desaconsejar el uso de la versión vieja, debe hacerse por la vía de las advertencias, pero no eliminarlo (aunque luego se hará, con el paso del tiempo) para no obligar a los clientes a modificar su código.

Otra forma de mejorar acoplamiento y cohesión es reducir la cantidad de parámetros de los métodos. Esto es fácil de hacer con variables compuestas u objetos que agrupen varios atributos.

También conviene, en esta misma línea, no incluir las opciones en los métodos que realizan acciones. Por ejemplo, la llamada a un método para imprimir no debería pasar el tipo de hoja, márgenes, etc., como parámetros. Si ello fuera necesario y no se puede hacer en el constructor, una solución es hacerlo estableciendo valores en forma sucesiva, como en el ejemplo siguiente:

documento.establecerHoja(A4):documento.establecerColorImpresión(rojo);documento.imprimir(); //el método no tiene parámetros para las opciones.

19

Diseño de implementación

Llamamos diseño de implementación a la elección de los atributos e implementaciones de métodos.

Conceptualmente, los atributos deberían mostrar sólo estado y los métodos sólo comportamiento con pocas bifurcaciones. A veces nos encontramos con métodos con grandes case/switch en los que se hace una cosa u otra en base al valor de un atributo, como en el ya visto:

if (unaFigura.getClass() == Elipse.class)(Elipse)unaFigura.dibujar();

else if (unaFigura.getClass() == Poligono.class)(Poligono)unaFigura.dibujar();

Diseño de jerarquías

Cuando construimos clases, una de las tareas a encarar es la construcción de jerarquías, por generalizaciones en la mayor parte de los casos, y en otros por especializaciones.Hay cuatro normas básicas de la generalización:

Al ir construyendo jerarquías, hay que poner la mayor parte de los atributos y métodos lo más arriba que se pueda, para evitar luego definiciones duplicadas.

Si una porción de código se repite en muchas clases hermanas, habría que generar un método y ponerlo en la clase base.

En forma más genérica, pero teniendo más cuidado, se puede generar un ancestro de todas las clases que tengan código en común, auque esto sólo es recomendable si se puede establecer la relación “es un”.

Yendo a un extremo mayor aún, se puede crear una clase base de dos clases semánticamente diferentes pero con responsabilidades similares. Esto suele llamarse varianza, y debe quedar bien documentado en el código. Creemos que esta extrapolación sólo debe hacerse en condiciones excepcionales, y cuando se demuestre fehacientemente la ventaja de hacerlo.

Pero además de las cuatro normas recién enunciadas, no hay que olvidar tres más, a menudo no consideradas:

Evitar generalizar todo lo que parezca generalizable de entrada. Como primera etapa, debemos resolver el problema que tenemos entre manos de la manera lo más simple posible.

20

Una nueva clase descendiente debe añadir o redefinir un método o agregar una nueva cláusula al invariante. Si no es así, quiere decir que no es necesaria, y corremos el riesgo de complicar la jerarquía sin un fin práctico. Esto parece evidente, pero muchas veces uno fue tentado a separa clases entre varones y mujeres cuando simplemente podría usarse un atributo booleano en la clase persona. Las jerarquías deben ser un aporte para dominar la complejidad, no para complicarla.

La herencia se debe usar siempre y cuando la relación entre los dos conceptos sea permanente.

Si bien es más sencillo describir una jerarquía de lo general a lo particular, no siempre ésta es la mejor manera de construirla:

A menudo se encuentran clases y la jerarquía se va construyendo por demanda.

Otras veces las generalizaciones van surgiendo de las clases concretas, al descubrir atributos o métodos en común.

Cuando se especializan clases adquiridas se procede por especialización.

Diseño del tratamiento de condiciones anormales

Excepciones a la herencia:

El uso de herencia con excepciones es una práctica cuestionable. Utilizar subclases para expresar excepciones. Pero las clasificaciones con subclases no permiten excepciones por diferentes categorías.Aves como animales voladores… pero ¿pingüinos, gallinas? Idiomas de Europa como indoeuropeos… pero ¿magiar, finés, turco? No se podrán clasificar las aves como americanas y europeas sin caer en la herencia repetida.Hay pocas soluciones en el terreno práctico. Se puede utilizar herencia múltiple, pero sólo en lenguajes que lo permitan. En el resto utilizar interfaces cuando las excepciones se dan a nivel de métodos. Un caso en discusión es que la circunferencia es una elipse con un radio menos. Hay problemas de atributos.

Los detractores de las excepciones para el manejo de errores aducen que un buen if que ponga en verdadero un parámetro de error es más claro y consume menos recursos (eficiencia).

Además, las excepciones nos obligan a hacer algo con ellas, mientras que el chequeo de condiciones lógicas puede evitarse, dando la impresión que no ha habido un error cuando en verdad lo hay.

21

Una mala idea es elevar una excepción si no hay error. Por ejemplo en una búsqueda, si no se encontró el valor buscado.

“Cuando todo falle, lance una excepción”.

Consejos al trabajar con excepciones:

Capturar las excepciones en el primer nivel que se pueda. De este modo tendrán un sentido mayor para el ámbito.

Usar excepciones en constructores, sobre todo para indicar que éste no terminó bien.

Una implementación de clase debería venir con las excepciones que puede disparar. Ponerlas en el mismo paquete.Cuando se produce una excepción luego de capturar recursos, a veces debemos liberarlos. El recolector de basura se ocupará de la memoria. El resto los debe liberar el programador en el bloque finally. Liberar en orden inverso a la adquisición.

Diseño por contrato

El método se basa en que existe un contrato entre implementador y cliente, y ambos esperan que la otra parte cumpla con él. Si esto es así, el comportamiento de una clase se puede especificar como si fuera un contrato. Adicionalmente, éste se materializa mediante precondiciones y postcondiciones de métodos, e invariantes de clase.

La mayor ventaja de este modelo es que se centra en qué hacen las clases más que en cómo lo hacen.

El cumplimiento de invariantes se debe dar entre sucesivas llamadas a métodos públicos, no así dentro de los métodos o entre métodos privados. Esto se debe a que son parte del contrato, y el contrato debe cumplirse ante un cliente, no internamente.Se dice que los momentos en que se cumple un invariante son momentos estables, y como mínimo deben ser: a la salida de un constructor, y antes y después de cada método no privado.

Patrones de diseño

El patrón de diseño resuelve problemas de diseño.

22

El patrón de programación es un algoritmo ya hecho y con nombre. Ejemplo: métodos de ordenamiento y búsqueda con nombres específicos.Los patrones se utilizan para la aplicación en distintos contextos, enseñanza, comprensión de partes de un sistema, comunicación con un vocabulario común.

Principios:

Encapsular lo que varía. Preferir la composición a la herencia. Programar usando interfaces, no implementaciones. Bajo acoplamiento en la interacción entre objetos. Mantener las clases abiertas para extensión y cerradas para modificación. Una sola responsabilidad por clase.

Un patrón de diseño es una solución a un problema en un determinado contexto. Nos indican cómo utilizar clases y objetos de modo de adaptarlos a la resolución de un problema. Se aplica a una sociedad de clases.

Ventajas:

Estructuras de diseño probadas previamente. Fácil interpretación de colaboraciones ya conocidas. Completitud frente a las soluciones caseras. Implementación directa sin análisis y diseño complejos. Facilidad de separar los aspectos que cambian de los que no cambian, dando

un mayor nivel de la abstracción. Introducen un lenguaje común para referirse a formas de construir software,

lo que a su vez favorece la enseñanza, el aprendizaje y el trabajo en grupos.

Se suelen denominar antipatrones a los diseños que han arruinado proyectos. Se usan para saber qué es lo que no hay que hacer.

Un patrón debe:

Ser concreto Resolver problemas técnicos Utilizarse en situaciones frecuentes Favorecer la reutilización No necesariamente poder ser traducido a código, pues resuelve familias de

problemas.

Un patrón se suele lograr luego de que varios diseños llevaron a uno en forma natural, no a la fuerza.

23

Caso I: Singleton

Lo que hace es definir una clase que sólo admite una instancia.

Debimos crear un constructor para que el compilador no lo cree por defecto y, declararlo privado.Observamos que el atributo que tiene el valor fue declarado estático, es decir, de clase.La clase EnteroUnico fue declarada final.El objeto unico se deberá crear con una llamada al método obtenerUnico, como sigue:

EnteroUnico unico=obtenerUnico();

Caso II: Pool de objetos

El pool de objetos es similar a Singleton, pero se trata de limitar la cantidad de instancias de una clase, no a una, sino a un número predeterminado.

Ej:

24

Faltaría definir la excepción ePoolVacio.

En este caso, si el programador desea una colección de elementos debe hacerlo definiendo esa colección en su aplicación y luego vinculando cada elemento.El problema que subsiste en esta solución es que no se pueden liberar objetos y colocarlos nuevamente en el pool.

Una solución es implementar el pool con un arreglo de objetos acompañado de otro que sirve para determinar si el elemento está disponible para obtenerlo.Permite devolver alguna instancia (la posición del vector) que ya no se utilizará.

Caso III: Objeto inmutable

El objeto inmutable es un patrón muy utilizado para resolver problemas derivados de la exclusión. Funciona bien en los casos en que no sea muy frecuente que los objetos de una clase cambien de estado y simultáneamente se dé que son de acceso frecuente.La solución : bastaría con declarar privados a todos los atributos, habilitar un constructor en el cual se pueda inicializar completamente el objeto, y luego proveer métodos públicos sólo para consultar propiedades del objeto, pero no modificarlas.

Caso IV: Contenedor

Hay ocasiones en que un objeto se puede representar como un conjunto de objetos de su mismo tipo o familia de tipos.

25

Podemos pensar en un programa de dibujo de figuras geométricas que permite agrupar figuras de modo tal que ese grupo se comporte como una única figura. Los métodos dibujar y colorear se podrían aplicar a una figura simple o compuesta.

Diagrama de clases:

Caso V: Estado abstracto

A veces necesitamos que un objeto cambie su comportamiento en función de sus cambios de estado. Incluso de un cambio de clase, como un peón que deviene reina en el juego de ajedrez.Cuando el cambio simplemente es de implementación de los métodos este patrón se llama Estado o Estado abstracto.

Diagrama de clases:

26

Implementación posible:

27

Caso VI: Marco de aplicación (Template Method)

Es el uso crudo del polimorfismo. Se basa en encapsular trozos de algoritmos. Se lo llama también Application Framework.

Los métodos a redefinir pueden ser abstractos o vacíos en la clase plantilla (son los afectados por los cambios; quienes definen detalles).El método plantilla debería ser final.

Delega en subclases la implementación de los detalles.

El patrón Marco de aplicación, a veces llamado Método plantilla, permite crear una aplicación o componente de software a partir de un esqueleto, reutilizando la mayor parte del código pero redefiniendo algunos métodos para parametrizar la aplicación de acuerdo a nuestras necesidades.

28

Un ejemplo práctico son las applets de Java. Para generar un applet, lo que se hace es crear una nueva clase descendiente de JApplet y redefinir el método init.

El método plantilla es quien llama a otros métodos, como el init de JApplet, que son los que hay que redefinir.

La idea es entonces definir un metodoPlantilla que invoque a otros métodos, como a y b. metodoPlantilla no es redefinible, mientras que a y b son abstractos y deben redefinirse.La clase cliente sólo define a y b, y una aplicación puede llamar al metodoPlantilla para que éste los invoque.

Implementación:

El método ordenar hace de método plantilla. mayor es el método que debe redefinirse.

29

Otro ejemplo: Arrays.sort(Comparable[]) donde se debe redefinir el compareTo() pero no mediante herencia.

Caso VII: Fábricas concretas y abstractas

El patrón fábrica de objetos sirve para encapsular la creación de objetos, sobre todo cuando hay varios constructores.

Un método que crea instancias cuyo tipo es una interfaz o una clase abstracta. Se mantiene una jerarquía de clases creadoras paralela a la jerarquía de clases a crear.El caso de iterator() en Java.Mantiene alta la cohesión, cumple con el principio de Única Responsabilidad.

Ejemplo de caso para uso de este método:

Supongamos que tenemos un marco de trabajo en el cual necesitamos un objeto aplicación que a su vez debe crear un objeto ventana donde los tipos de aplicaciones y ventanas particulares no han sido desarrollados todavía.

Lo que se hace es centralizar la creación de varios tipos de objetos en una clase unificada que es como una interfaz para la creación de objetos.

Posible diagrama de clases:

No siempre es necesario que Fabrica sea una clase abstracta.

En la clase Fabrica, el método operacion1 va a tener una línea que llame al método creador de objetos como la que sigue:

30

unProducto=crear();

A su vez, FabricaConcreta1 implementará el método crear de la siguiente forma:

public ProductoConcreto1 crear(){return new ProductoConcreto1();

}

Y algo similar hará la clase FabricaConcreta2.

Casos en que se usa:

La clase a ser instanciada puede cambiar. No es fácil redefinir el método en el cual se crean los objetos.

Fábrica abstracta es más complejo. Su objetivo es el encapsulamiento de la creación de familias de objetos.

Supongamos que necesitamos crear un botón y un menú para dos tipos de interfaces de usuarios gráficas: Windows y Macintosh. Una posible solución sería un código como el que sigue:

Boton b;if (interfaz == “Windows”) b=new BotonWindows();if (interfaz == “Macintosh”) b=new BotonMacintosh();

La mejor solución es juntar todos los métodos creadores en una clase FabricaGUI y luego redefinirlos.

Diagrama de clases:

31

Observamos que el patrón fábrica abstracta también decide en forma estática su forma de crear objetos.

Caso VIII: Clase adaptadora

A menudo es necesario tratar con el mismo nombre a métodos de clases diferentes que incluso pueden tener signaturas diferentes.

Supongamos, por ejemplo, que tenemos una clase Libro, provista por un servicio remoto denominado LibreriaVirtual, que devuelve el precio de un libro llamando al método precio. Simultáneamente, el servicio remoto LaGranTienda ha implementado una clase Articulo, que incluye libros, y que tiene un método esLibro, que indica si es o no un libro, y otro precioArticulo, que devuelve el precio de un artículo. ¿Qué ocurriría si se desea acceder a precios de libros con una sintaxis uniforme, mediante un método precioLibro?

Una solución sería construir una interfaz que luego se implementaría en una clase adaptadora, como muestra este fragmento en Java:

public interface ConsultaPrecios {double precioLibro();}

Diagrama de clases:

32

Implementación:

Caso IX: moldes de métodos para invariantes

33

Un invariante puede verse como una condición que se debe cumplir en todo objeto antes y después de cada llamada a un método.

Observamos que el método que hay que invocar para que se verifiquen los invariantes es el mover definido en FiguraAbstracta.

Caso X: Proxy

34

Proxy es una clase que hace de interfaz de otra. Es decir, los métodos del Proxy simplemente reemplazan con el mismo nombre a métodos de otro objeto que por alguna razón no es accedido directamente.

La ventaje principal es que los clientes actúan hablando con un intermediario como si lo hicieran con el verdadero objetivo, y descansando en este intermediario toda la complejidad de la comunicación con el objetivo verdadero.

Proxy remoto: suele servir como caché o copia local, evitando que haya que ir a buscar cada vez al objeto remoto.

Proxy virtual: cuando no se crea el objeto meta sino hasta que sea realmente necesario.

Proxy de proyección: para controlar el acceso al objetivo. Proxy de sincronización: para gestionar acceso de varios clientes al objetivo.

Estructura de clases:

Caso XI: Fachada y Mediador

En ocasiones se desea simplificar el acceso a varias clases, mostrando al cliente todos los métodos necesarios de varias clases. El padrón que resuelve esto se llama Fachada.

Se puede extender Fachada a lo que se conoce como patrón Mediador. Este se suele usar para comunicar cambios de estado entre objetos reduciendo el acoplamiento y aumentando la cohesión. Sirve en todos aquellos casos en que hay muchas dependencias entre clases que convierten al diagrama de clases en un grafo inmanejable.

35

Diagrama de clases:

El Mediador registra y maneja eventos, mientras los observadores son interfaces que reciben notificaciones de cambios de estado de los clientes. El Mediador es quien implemente las interfaces observadoras para recibir las notificaciones, así como la lógica de las respuestas, e interactúa con los objetos clientes.

Caso XII: Visitador o Recorrido

Un Visitador o Recorrido es un iterador que permite hacer una función en cada nodo de la colección, tal vez aplicando un filtro.

36

Debemos vincular la lista con la Visita que se va a aplicar. Una posibilidad simple sería que ListaConRecorrido implemente la interfaz Visita y defina los métodos

37

aplicar y cumpleFiltro. Un tipo de lista siempre quedaría atado a ser recorrido con el mismo filtro y aplicando la misma función a sus elementos.

Caso XIII: Observador

Consecuencias:

Bajo acoplamiento entre sujeto y observadores.o El sujeto no se ocupa de los observadores.o Ni cambia si se registran observadores de tipos diferentes.o Lo único que sabe es que implementan una determinada interfaz.o Los observadores se suscriben y desuscriben libremente a una lista de

referencias. Soporte de broadcasting

o Agregado y remoción dinámica de observadores.

Modelo “push”:

Sujeto envía toda la info que requieren los observadores. Mayor acoplamiento: provoca una interfaz de observador específica. Se envía información detallada, sin importar la incumbencia para los

observadores.

38

Modelo “pull”:

Sujeto no envía info; sólo notifica. Observadores consultan luego lo que les interesa. Puede ser ineficiente. Bien desacoplado.

Modelo con eventos:

Se registran observadores sólo para ciertos eventos o temas (uno o más).

Es de gran utilidad en MVC. En general es aplicable a cualquier desarrollo de sistemas en capas.

La idea del patrón Observador es separar dos objetos: uno que es observado y otro u otros que observan, de modo que los segundos pueden cambiar su estado y comportamiento en función de cambios en el primero.

Esta implementación define una clase Observable, que permite crear clases derivadas de objetos observados. Observable hace un seguimiento de una lista de objetos observadores que desean ser informados de cualquier cambio en el observado, y notifica con un método denominado notifyObservers.

También define una interfaz Observer, que expone una sola función denominada update, llamada por el objeto observado cuando decide que es hora de notificar cambios.

Es el objeto observado quien decide cómo y cuándo hacer la actualización. Para ello, Observable tiene un atributo lógico protegido que indica que hubo un cambio digno de ser notificado: este atributo se accede con las funciones hasChanged y setChanged.

Métodos de Observer y Observable en Java:

39

Deben definirse una clase descendiente de Observable que, ante determinados cambios, invoque los métodos setChanged y nofifyObservers. En general los métodos de Observable no se redefinen. También se deberá construir una clase que implemente Observer, escribiendo el método update.

Caso XIV: Orden y Deshacer

El patrón llamado Deshacer busca una solución al problema habitual de permitir a un usuario de una aplicación volver atrás anulando lo que acaba de realizar, en general con la posibilidad de volver a hacerlo (rehacer) y de varios retrocesos sucesivos.

Las órdenes (acciones) pueden implementar una interfaz:

40

interface Accion {void hacer();void deshacer();

}

En un editor de texto podríamos tener una clase que representa la eliminación de una línea.

Lo que se hace es almacenar lo que luego hay que reponer. Cada orden guardará cosas diferentes, que le sirvan par restaurar el estado a la situación anterior.

Por lo tanto, bastaría con guardar siempre en una variable general del estado de la aplicación la última orden ejecutada, con un atributo como el que sigue:

Acción ultimaOrden;

41

Lo que acabamos de hacer sólo admite un nivel de deshacer. Si se quiere agregar más niveles habrá que usar una lista histórica, en vez de guardar solamente la última orden enviada por el usuario, con una estructura de datos que permita hacer esto.

Como consecuencia, la implementación de deshacer y rehacer será algo como:

42

Caso XV: Decorador

El padrón Decorador consiste en usar objetos en capas, de modo que se les pueda agregar responsabilidades dinámica y transparentemente.

Supongamos, por ejemplo, que una fábrica de automóviles produce unote sus modelos en tres variantes, llamadas sedán, coupé y familiar, y que cada una tiene un precio básico de venta, sin opcionales. Supongamos igualmente que a cada variante se le pueden agregar opcionales como techo corredizo, aire acondicionado, sistema de frenos ABS, airbag y motor de 16 válvulas, y que cada uno de estos opcionales tiene un precio que se suma al básico. En este caso, cada auto vendrá caracterizado por su variante y podrá tener ninguno, uno o más opcionales.

La solución tradicional consistiría en usar herencia, creando una clase para cada combinación posible. Sin embardo, con 3 variantes y 5 opcionales, las combinaciones posibles son 3x25, es decir 96 clases. En estos casos, aún cuando la codificación de Decorador es más compleja, es mejor que usar herencia.

43

44

45

public double costoTotal() {return basico.costoTotal() + costo;

}}

46

Luego, para instanciar un sedán con techo corredizo y aire acondicionado, por ejemplo, se hará:

Componente a = new TechoCorredizo(new AireAcondicionado(new Sedan()));

La estructura básica del patrón Decorador es:

El desarrollo orientado a objetos como conjunto

Una serie de requisitos mínimos para ser “orientado a objetos”

Características mínimas que debe tener un lenguaje o método para ser llamado “orientado a objetos”:

Procesos aplicables a todas las fases del desarrollo, sin discontinuidades. Clase como concepto central. Clases como únicos tipos de datos, salvo unos pocos tipos básicos. Métodos y/o propiedades como único mecanismo para comunicación y

provisión de servicios entre objetos. Soporte para el encapsulamiento, entendido como abstracción más

ocultamiento de implementación. Facilidades para especificar o implementar el modelo contractual. Comprobación de tipos estática. Soporte de herencia simple. Herencia múltiple o interfaces (por lo menos una de las dos). Polimorfismo, incluyendo redefinición, objetos polimorfos y vinculación

dinámica. Información sobre el tipo de un objeto durante la ejecución. Soporte para clases y métodos abstractos.

47

Colecciones con elementos genéricos, aunque sea restringidos por la jerarquía.

Gestión automática de memoria y recolección de basura. Manejo de excepciones basado en clases. Manejo de concurrencia basado en clases para los procesos o hilos. Manejo de persistencia.

Conceptos de desarrollo de software antes y después de la orientación a objetos

Ciclo de vida tradicional del software

El ciclo de vida tradicional, también llamado en cascada, se basa en ir cumpliendo una serie de etapas, cada una separada de las otras, de modo tal que recién se empieza una etapa cuando se terminó la anterior. Cada etapa es efectuada por un grupo de personas especializadas en esa tarea, y una vez terminada la misma, se genera una serie de documentos que tomarán como entrada los trabajadores de la siguiente etapa.

Ha habido muchas definiciones de las etapas del desarrollo en cascada, pero podemos esquematizarlas de la siguiente forma:

Requerimientos: qué hay que hacer, y si es necesario hacer algo y por qué, con definición de la frontera del sistema para determinar qué queda dentro del mismo y qué fuera. Se basa en entrevistas con los futuros usuarios.

Análisis: propuesta de solución a los requerimientos, independientemente de la tecnología y postergando toda referencia a detalles técnicos.

Diseño: cómo hacer lo que se estableció en el análisis; en otras palabras, fijar la arquitectura global, las clases necesarias, tipo de persistencia, etc.

Implantación: llevar el diseño a código ejecutable, crear las bases de datos, etc. Se suele incluir el despliegue del producto sobre el hardware.

Pruebas y depuraciones: comprobar que lo implementado cumpla con los requerimientos iniciales y corregirlo en caso contrario.

Mantenimiento post entrega: incluye corrección de errores, evolución por cambio de requerimientos y preservación para mantenerlo operable, una vez que el sistema se encuentra en producción.

Muerte del proyecto: se produce por obsolescencia, porque se termina el sistema y no es necesario agregarle más nada, o cuando es muy caro de mantener.

Diagrama de actividades del ciclo en cascada, sin mantenimiento:

48

El ciclo de vida en cascada tiene varias desventajas que hicieron que se lo fuera ajustando:

Impide comenzar una etapa hasta que la anterior no está totalmente concluida, retrasando todo el proceso.

Cuesta mucho cambiar algo sobre una etapa ya terminada, o se hace a costa de gran cantidad de burocracia y documentación. A menudo se intentan soluciones locales, que tratan de remediar en el diseño lo que viene mal del análisis o en la implementación lo que viene mal del diseño.

Recién luego de la implantación se prueba el sistema. Por lo tanto, cuando surjan los inevitables errores, habrá que comenzar a analizar en qué etapa se produjeron, y a partir de allí modificar lo que sea necesario.

El usuario final recién ve el sistema una vez que está terminando, por lo que generalmente se siente poco consustanciado con el desarrollo y no puede advertir errores de concepción hasta la entrega total.

Esto lleva a una serie de consecuencias negativas del uso de las metodologías estructuradas que se pueden resumir así:

Entrega de productos que no satisfacen los deseos de los usuarios y que no pueden usarse con facilidad.

Imposibilidad de poder responder con rapidez y de manera efectiva a los cambios de requerimientos.

Demora en los proyectos novedosos.

49

Desarrollo incremental

La visión orientada a objetos plantea hacer requerimientos, análisis, diseño, implantación y prueba, en forma iterativa. Esta modalidad se denomina desarrollo incremental o en espiral.

Las ventajas son:

Aumenta el compromiso de los usuarios al poder mostrarles versiones parciales (cascadas parciales).

Los miembros del equipo de trabajo ven que se cumplen metas parciales y trabajan más tranquilos aumentando la productividad.

Permite hacer frente a cambios de requerimientos. Los errores aparecen antes, en las pruebas de cada iteración. Esto reduce el

riesgo y evite la carga emocional negativa que provoca el testeo al final del desarrollo.

Hay más continuidad entre las fases del desarrollo, de modo que una persona puede participar en varias de ellas. Esto implica que en las metodologías incrementales no se utilizan los mismos roles profesionales de los métodos estructurados. Sin embargo, surgen nuevos roles, como lo s de arquitecto e integrador.

El analista se confunde con el diseñador, y este con el programador.

Desarrollo con prototipos

En el desarrollo mediante prototipos se construye, en primer lugar, un prototipo del sistema, siguiendo todas las etapas, incluso la de prueba y entrega al usuario. Una vez probado y detectados los errores, se sigue a la siguiente iteración, en la cual se corrigen las fallas y se sigue adelante con un nivel de refinamiento mayor. Y así sucesivamente, hasta que se llega al sistema final, que no es otra cosa que el último de los prototipos.

Un prototipo es toda versión preliminar, intencionalmente incompleta y en menor escala de un sistema.

Diagrama de actividades que correspondería a este método:

50

Notas sobre mantenimiento

Decimos que mantenimiento es la tarea que consiste en reparar, extender, mejorar un producto o adaptarlo a nuevos ambientes, pero siempre después de haber sido entregado a un cliente. La reparación tiene que ver con la depuración que es la misma tarea que se hace luego de las pruebas.

Una introducción al análisis de riesgos

Los riesgos que puede afrontar un proyecto de software se pueden clasificar en:

Funcionales o de requerimientos: que no se construya el sistema que se necesita. Se puede afrontar con un buen modelo de casos de uso y del dominio, además de establecer desde el principio las prioridades funcionales o del cliente.

Técnicos: se relacionan con el desconocimiento de tecnologías nuevas, dificultades de integración entre tecnologías y componentes de diferentes fabricantes o con diferentes estándares de interfaces. Una forma de disminuirlo es probar las herramientas al inicio del desarrollo, capacitarse y elegir la que más se adapte.

Políticos: originados en fuerzas políticas que se oponen al proyecto.

51

Otros: comerciales, financieros, de habilidades, etc.

Entropía, refactorización y optimización

El término entropía tiene el significado de un desorden gradual e imparable al que se llega por inercia y del cual es muy difícil salir.Se suele dar cuando un buen diseño va evolucionando por sucesivas adaptaciones y se vuelve incomprensible e imposible de mantener.El proceso por el cual se modifica la relación entre módulos, en parte para lograr buenas características de acoplamiento y cohesión, se denomina factorización, y en general consiste en la separación de partes de módulos en otros módulos.Como la factorización suele ser una tarea de diseño, cuando se la hace después de la programación del módulo se la suele llamar refactorización.La idea de la refactorización es cambiar código sin cambiar la semántica, para reducir la entropía del software.

Refactorización:

Mejorar el diseño del código ya escrito. Modificando la estructura interna, sin modificar la funcionalidad externa. Un poco como las optimizaciones.Ejemplo simple: Eliminar código duplicado.Mejorar el código para que sea más comprensible para modificaciones, depuraciones, optimizaciones. Mantener alta calidad del diseño para que no se degrade. A la larga, aumenta la productividad.Se hace antes de modificar código existente. Siempre después de incorporar funcionalidad. Antes de optimizar, durante depuraciones, durante revisiones de código. Siempre, si se hace TDD o XP.Condiciones previas: Riesgo alto. Máxima: “si funciona, no lo arregle”. Un paso por vez. Pruebas automatizadas (escribirlas antes de refactorizar y correrlas luego de cada pequeño cambio).

Cuestiones y soluciones:

Código duplicado: Extraer un método. Extraer y llevar arriba de la jerarquía. Extraer una clase, cuando no hay jerarquía común.

Método largo: Extraer métodos. Se pueden ayudar con comentarios y con partes condicionales y ciclos.

Clase grande, con muchas responsabilidades: Extraer clases. Extraer subclases.

52

Lista de parámetros larga: Crear clases para los parámetros. Eliminar el parámetro y agregar una llamada a método.

Cambios divergentes: Separar las clases cuya necesidad de cambio tenga frecuencias distintas o provenga de necesidades diferentes.

Shotgun surgery: Es lo opuesto a lo anterior. Cuando cada cambio me obliga a tocar muchas clases. Mover atributos o métodos para crear una única clase.

Envidia de características: Cuando una clase se la pasa llamando a métodos de otras clases. Poner los métodos en las clases que los usan.

Clases sin comportamiento: Pueden provenir de refactorizaciones anteriores. Pueden existir, pero no es bueno.

Ifs y switchs abundantes: Herencia y polimorfismo. Patrones de estado y estrategia. Otras soluciones de catálogo.

Jerarquías paralelas: Juntar clases.

Herencia especulativa: Colapsar la jerarquía.

Clases que son alternativas pero tienen interfaces diferentes: Renombrar métodos y otras más complicadas.

Flexibilidad:

La flexibilidad oscurece el código y agrega complejidad. En general se flexibiliza según lo que se espera que cambie. Es el enfoque de los patrones de diseño.

53

Algunas metodologías de desarrollo orientado a objetos

Métodos, notaciones y herramientas

Hay tres aspectos a analizar cuando se encara un desarrollo de software: los procesos, las notaciones y las herramientas.

Un método o proceso define quién debe hacer qué, cuándo y cómo se deben realizar las distintas tareas, en el desarrollo de software. Ejemplos de métodos son el Proceso Unificado, Extreme Programming, Scrum, etc.

Una notación es un lenguaje de especificación de modelos, que permite diagramar los conceptos principales del software a construir. Una notación conocida es UML.

Una herramienta es una aplicación que facilita el desarrollo y en ocasiones puede generar parte del código.

Métodos:

1. Las grandes metodologías, que se utilizan en grande proyectos y suelen ser inaceptablemente pesadas para sistemas pequeños o medianos. En este grupo destaca el Proceso Unificado de Desarrollo de Software (PUDS o PUD).

2. Los métodos ágiles (también evolutivos o adaptables), que permiten organizar desarrollos medianos sin caer en burocracias paralizantes. Pueden verse incluso como una alternativa a carecer de metodología (o metodología de “codifica y corrige”), que es lo que suele ocurrir cuando se descarga un proceso muy pesado. En este grupo destaca, como el más conocido, Extreme Programming (XP).

Lo fundamental es entender que no todas las metodologías sirven para cualquier proyecto, ni hay una sola que se pueda aplicar a todos.

El Proceso Unificado de Desarrollo de Software

Define cinco flujos de trabajo elementales, denominados requisitos, análisis, diseño, implementación y prueba. No se refieren al tiempo. Son asociados a tareas. Simultáneamente definen cuatro fases: inicio, elaboración, construcción y transición. Se refieren al tiempo. Asociadas a hitos y objetivos. Cada fase puede realizarse en una o más iteraciones. Al final de cada iteración tendremos una versión del producto.La idea del proceso es que cada fase incorpore un poco de cada filtro, aunque obviamente las fases iniciales van a tener un mayor proporción de requerimientos y

54

de análisis y casi nulo de implementación y pruebas, mientras que en las finales será lo contrario.

Las fases del Proceso son:

Fase de Inicio: planifica el proyecto, se estudia su factibilidad y se delimita su alcance. Se estudian los casos de usos con los usuarios y construyen prototipos para validar conceptos.

Los objetivos del hito de finalización son:o Haber definido los objetivos del sistema.o Tener una idea aproximada del costo.o Establecer la factibilidad del proyecto (si conviene proseguir).o Obtener el modelo de dominio, que indique cómo se relacionan los

diferentes conceptos. Qué hacer:

Planificar el proyecto. Estudiar factibilidad. Delimitar alcance.

No suele ser una fase iterativa.

Fase de Elaboración: se establece la arquitectura y se analizan los riesgos mayores. Se hacen las pruebas que se correspondan con el diseño e implementación. Se exploran escenarios y arquitecturas con los usuarios.

Los objetivos del hito son:o Haber acotado los riesgos.o Tener definida la arquitectura básica y el plan de

iteraciones de construcción, indicando qué casos de uso se realizan en qué iteraciones. Empezar por los prioritarios para el cliente y los de mayor riesgo.

Qué hacer: Establecer las “arquitecturas”. Analizar los riesgos mayores.

Puede ser una fase iterativa.

Fase de Construcción: Se desarrolla un producto completo que esté listo para ser utilizado como versión beta (es el objetivo final del hito). Es la fase iterativa por excelencia. Para reducir el riesgo funcional, cada iteración debe ser un miniproyecto que debe terminar con una entrega al usuario y pruebas de sistema. Se deben terminar los requisitos y el análisis que reste y se completará el grueso del diseño e implementación, así como sus pruebas. Es decir que involucra al análisis, el diseño y la implementación. Se pueden hacer cambios de arquitectura. Se

55

debe ir ajustando la planificación de las iteraciones. Se van ensamblando partes ya probadas sobre un código también probado.

Fase de Transición: Es la fase en que el producto se despliega para ser utilizado. Aquí comienza una serie de puestas a punto y adaptaciones a necesidades no previstas de los usuarios que surjan de las pruebas beta. El objetivo del hito final es el lanzamiento del producto. No se añaden funciones nuevas pero se desarrolla para optimizar y depurar. Se hacen despliegues graduales en las etapas anteriores.

El Proceso parte de un modelo de casos de uso. Las iteraciones se hacen implementando casos de uso en forma completa. Y las pruebas se hacen también verificando las especificaciones de casos de uso.

Por eso, los casos de uso se capturan y definen en requisitos, se realizan en análisis, diseño e implementación y se verifica que se satisfagan en las pruebas.

En todas las etapas del desarrollo puede decidirse no conveniente seguir adelante con el mismo ya que el sistema no daría resultados esperados por el precio esperado. Esta tarea se suele denominar estudio de factibilidad.

56

Métodos ágiles

Son métodos no predecibles ya que son muy cambiantes en lo que respecta a requerimientos. Los clientes pueden decidir a último momento que un requerimiento no es más tenido en cuenta o que el software necesitaría de otros requerimientos para satisfacer sus necesidades, por lo tanto es un método cambiante de requerimientos. Se adaptan a cada desarrollo. Muy comunes en el software. No es para cualquier cliente ni para cualquier informático. El diseño y la programación van muy acoplados. No son compatibles con tiempos, alcances o presupuestos fijos.

Un ejemplo de estas metodologías ágiles es Extreme Programming (XP).

57

Extreme Programming

Lleva al extremo las buenas prácticas.

Los objetivos son bajar el riesgo, permitir cambios de especificaciones durante el desarrollo, favorecer la comunicación con el cliente y que la inversión crezca gradualmente.

Máximas:

Como probar programas es bueno, se hacen pruebas unitarias y funcionales todo el tiempo, al punto que el desarrollo es dirigido por las pruebas, y éstas se diseñan antes de codificar.

Como hacer pruebas de integración es importante, la integración sigue inmediatamente a las pruebas unitarias. La integración frecuente permite que no sea cada vez más complicado integrar código de varias fuentes.

Como los diseños de software son cambiantes, el diseño del sistema evoluciona junto con la programación, y lo hacen los propios programadores.

Como revisar la calidad del código es recomendable, se revisa código todo el tiempo y se hacen refactorizaciones, que implican cambios de diseño para hacerlo más simple, pero sin cambiar funcionalidad.

Como los estándares de codificación permiten una mejor comunicación y reducen los errores, se fijan estándares precisos y estrictos.

Como es bueno que el sistema sea simple, se busca la simplicidad siempre, diseñando sólo lo que se necesita en el momento. Este tal vez sea el principio fundamental y más controvertido de XP.

Como los requerimientos de arquitectura pueden ser cambiantes, la arquitectura se refina constantemente.

Como el desarrollo incremental es positivo, se hacen iteraciones lo más cortas que sea posible: se diseña una pequeña porción, se la codifica y se la prueba. La idea es que dentro de cada iteración nos debemos preocupar de lo que estamos haciendo, sin hacer nada por adelantado o pensando en futuras iteraciones.

Se opera sobre el alcance como variable de ajuste, como en las otras metodologías ágiles.

Como es muy frecuente que un proyecto de desarrollo se suspenda en un determinado momento por falta de presupuesto, se implementa primero lo que tiene mayor valor para el cliente. De ese modo, lo que queda sin desarrollar es siempre menos importante. La primera iteración debe llegar a implementar un esqueleto del sistema, con una serie de funcionalidades básicas.

58

Como es importante tener una comunicación frecuente con el cliente, siempre debe haber un cliente acompañando el desarrollo. El cliente es fundamental para escribir pruebas funcionales y priorizar tareas.

Como dos cabezas piensan más que una, y como refuerzo de las prácticas anteriores, los programadores trabajan de a dos: uno escribe, el otro piensa en mayor escala, buscando simplicidad, errores y formas alternativas de solución del problema. Las parejas pueden intercambiarse a lo largo del desarrollo, y dentro de cada pareja los roles van cambiando a lo largo de cada tarea a desarrollar. De todas maneras, cada pareja es responsable de una tarea, y no supone que haya intercambios hasta no terminarla. Este es el aspecto más mencionado y a la vez menos conocido de XP.

El aspecto que más resistencias genera, y que es central en XP, es la simplicidad llevada al extremo. La recomendación de XP es que cualquier código duplicado debe refactorizarse de modo de eliminar duplicaciones. Afirma que si una clase, un método o cualquier porción de código no sirven para el sistema actual, se debe eliminar.

No obstante, XP afirma que hay que eliminar toda la flexibilidad que no demuestre su utilidad en lo inmediato, evitando resolver hoy los problemas de mañana.

Razones de XP:

Se evita agregar complejidad que luego dificulta las refactorizaciones, la comunicación y las depuraciones.

Se evita implementar lo que no sabe si se va a usar (y en el 80% no se usa). Se mantiene baja la inversión inicial en el proyecto, de modo que si éste se

abandona o se trunca no habrá costado tanto. Y aunque estos extremos no ocurran, la inversión inicial es la que tiene mayor costo financiero, por lo que mantenerla baja es siempre una buena política.

Mejora la eficiencia de ejecución del sistema, debido a que el código ejecutable permanece más chico.

XP no es aplicable:

A proyectos que, por razones organizacionales o presupuestarias, necesiten especificaciones precisas desde el comienzo.

A equipos de trabajo con más de 20 programadores (en número ideal es 10). A equipos de trabajo que deben trabajar separados, algo muy en boga de

nuestros tiempos. Cuando la tecnología es un impedimento por los altos tiempos de

compilación, prueba o integración. A proyectos con precio, plazo y alcance fijos.

59

Una introducción a las pruebas en programación orientada a objetos

Las pruebas en las metodologías de desarrollo incremental

Los métodos incrementales de integración y prueba evitan el colapso de los sistemas en su última fase de implementación, ya que permiten probar temprano las funcionalidades cruciales de los mismos. Podrán ver el progreso del sistema desde su estado más rudimentario hasta su eventual completitud.

Las metodologías más modernas hacen un desarrollo iterativo en todas las fases. En este sentido, las pruebas son un proceso continuo que comienza en la primera iteración.

Las pruebas unitarias se pueden aplicar a clases, bloques y paquetes. A veces es mejor probar paquetes enteros, ya que las clases en un paquete suelen interactuar bastante o también testear la herencia y el polimorfismo y el ciclo de vida del objeto. Pueden usar aserciones.

Las pruebas de integración se aplican a bloques, paquetes, casos de uso y subsistemas.

Si utilizamos prototipos, cada prototipo es como un mini sistema, por lo que en su tramo final se le deben efectuar pruebas de sistema y de aceptación.

El Proceso Unificado recomienda probar casos de uso. Estos casos se llaman casos de prueba y suele haber una relación uno a uno con los casos de uso de las etapas de requerimientos y análisis. Las pruebas de caja negra se pueden preparar desde el análisis. Las de caja blanca se pueden implementar mientras se diseña. Las pruebas de sistema y aceptación se realizan en la etapa de transición. El Proceso Unificado pone especial énfasis en la planificación de las pruebas de cada iteración.

Las pruebas en Extreme Programming

Como ya hemos dicho, XP propone que las pruebas sean automatizadas y que el código de las pruebas unitarias se escriba antes del código a probar.

Las pruebas son una forma alternativa de establecer el contrato de la clase con sus clientes, adicional a la definición de invariantes, precondiciones y postcondiciones.

Pruebas y programación orientadas a objetos

60

La POO puede traer problemas inesperados en la prueba de software.

Al cambiar la implementación de una clase, no sólo se deben probar los métodos en forma unitaria, sino también realizar pruebas de integración.

Al modificarse una clase base, debemos probar nuevamente las subclases. Al agregar una subclase, tenemos que testear los métodos heredados. Al redefinir un método, el conjunto de datos de prueba original puede no ser

adecuando para el método nuevo. Un método no redefinido puede necesitar un nuevo conjunto de datos de

prueba, ya que puede invocar métodos redefinidos.

Modelado y documentación con UML

61

Documentación interna

Documentación interna y comentarios

62

Documentar en el propio código facilita mantener la documentación actualizada. Además, el hecho de que la documentación esté cerca de lo que se documenta siempre una ventaja. A esto se lo llama documentación interna.

Recomendaciones:

No usar comentarios como remedio para el código poco claro. Ante todo, código debe ser bien legible.

Los comentarios muy abundantes sólo deben ir como prólogos de secciones muy críticas, poco claras, avisos para el mantenimiento y efectos colaterales locales.

Evitar los comentarios del tipo “no borrar”, que en general acompañan una línea de código ininteligible. Con el tiempo nadie sabrá por qué no hay que borrar la línea, y el solo hecho de figurar el comentario indica un código oscuro, mal escrito o una pobre documentación.

El caso de javadoc

El programa javadoc, que toma algunos de los comentarios que se colocan en el código con marcas especiales y construye un archivo HTML con clases, métodos y la documentación que corresponde.

Se inscribe en comentarios que empiezan con /** y terminan con el habitual */. Se pueden escribir comandos precedidos de @. Se debe poner la documentación inmediatamente antes de la declaración respectiva.

javadoc procesará la documentación que se escriba para atributos y métodos públicos y protegidos, sin emitir nada para los privados y de paquete.

Sólo deberían omitirse los formatos de títulos <h1> y <hr>.

Ej:

/** <h2> La clase que sigue es muy importante: </h2>*/

Cuando se quiere indicar una referencia a otra clase se puede usar el comando @see:

@see nombre-de-clase@see nombre-de-clase-con-todos-sus-calificadores@see nombre-de-clase-con-todos-sus-calificadores#nombre-método

De este modo, aparecerá un vínculo del tipo “véase también”.

63

La información de versión se puede indicar con @version:

@version información-de-versión

La información a colocar puede ser cualquiera que nos parezca interesante.

Con el comando @author se puede poner también cualquier información sobre el autor o autores de una clase:

@author información-del-autor

Con el comando @since se puede informar desde cuándo o qué versión se encuentra vigente una determinada capacidad de la clase.

Para documentar parámetros, valores devueltos y excepciones de un método se usan los comandos @param, @return y @throws:

@param nombre-de-parámetro descripción@return descripción@throws clase-de-excepción-con-sus-calificadores descripción

Igualmente se pueden incluir en la documentación avisos de supresión de una determinada característica en versiones futuras, de modo de que su uso vaya siendo abandonado, con el comando @deprecated, que provoca un aviso del compilador.

Estándares de nomenclatura

Existen estándares para entender mejor un código que ya fue escrito, para que a futuro se entienda nuestro código por otras personas o por nosotros mismos. Cada lenguaje tiene un estándar de nomenclatura específico, pero a la vez puede utilizarse cualquiera que sea más conveniente.

Persistencia

Introducción

Persistencia y objetos persistentes

Llamamos persistencia a la capacidad de un objeto de trascender el tiempo o el espacio. En la programación tradicional se suplía con la entrada y salida de datos.

64

Permite que un objeto pueda ser usado en diferentes momentos a lo largo del tiempo, por el mismo programa o por otros, así como en diferentes instalaciones de hardware en el mismo momento. Guardando el estado de un objeto y poder enviarlo por una red.

Un objeto persistente es aquél que conserva su estado en un medio de almacenamiento permanente, pudiendo ser reconstruido de modo tal que se encuentre en el mismo estado en que se lo guardó. Al objeto no persistente lo llamamos efímero.

Cuando se trate de guardar el estado completo de una aplicación en un determinado momento, la mejor solución es almacenar todo el estado en un solo objeto y luego guardarlo. De este modo, la persistencia sirve también para guardar estados de objetos complejos. Separación de identidad y estado.

En UML, el que una clase sea persistente se indica mediante un valor etiquetado denominado persistent, como en el ejemplo que sigue:

Separación de la capa de acceso a datos

Como ya hemos dicho en el capítulo de diseño de arquitectura, se puede pensar un programa como la conjunción de tres componentes o capas:

Capa de acceso a datos: se encarga del acceso a bases de datos y archivos, para consulta, almacenamiento o persistencia.

Capa de reglas del negocio: implementa la lógica de la aplicación. Capa de interfaz de usuario: se encarga de presenta la información a los

usuarios y recibir sus solicitudes.

El objetivo principal de separar un sistema en capas es el de permitir cambios de implementación en alguna de las mismas sin afectar a otras.

65

Permitir que un mismo sistema informático pueda almacenar sus datos en diferentes tipos de bases de datos o archivos, o exportar hacia computadoras que estén trabajando con otra plataforma.

Normas de la persistencia

Hay tres principios de la persistencia:

De la ortogonalidad del tipo de datos: para todo tipo debe haber persistencia. De la identificación persistente: el diseño de la persistencia no debe

depender del universo conceptual del sistema. De la persistencia independiente: los programas no deben variar según la

longevidad de la información que manipulan.

Problemas con objetos compuestos

Los tipos de persistencia en cuanto a la forma de manejar referencias se clasifican en:

Simple: no preserva referencias entre los objetos almacenados. Isomórfica: preserva las referencias. Polimórfica: es una persistencia isomórfica, pero la recuperación del objeto

se puede hacer sin conocer previamente su tipo.

Entrada y salida en Java

Java ofrece cinco clases principales para el manejo de flujos de entrada y salida. Las clases InputStream, OutputStream y sus derivadas están orientadas al manejo de bytes, y existen desde las primeras versiones del lenguaje. Reader y Writer, equivalentes a las anteriores, pero para entrada y salida de caracteres Unicode.

Diagrama de los descendientes de OutputStream:

66

El archivo System.out que venimos utilizando para las salidas por consola es una instancia de la clase PrintStream. Dicha clase tiene un método print y otro println, siendo el segundo igual al primero, salvo por el hecho de que inserta un carácter de fin de línea luego de imprimir. Estos métodos están sobrecargados para todos los tipos primitivos y para String, pudiendo utilizarse también automáticamente por cualquier clase que redefina el método toString de Object. Igualmente hay otro archivo estándar de la clase PrintStream, el flujo de salida de errores System.err.

La que sigue es la jerarquía de clases derivadas de InputStream:

Writer y Reader y sus descendientes:

El siguiente ejemplo muestra un fragmento de código que lee un archivo por líneas y lo muestra por consola:

67

Otra característica adicional es el paquete java.nio (cambio en versión 1.4), que coexiste con java.io. Mejor desempeño. Mejor soporte de redes.

ByteBuffer tiene algunas facilidades, como métodos para convertir los bytes en datos de tipos primitivos, llamados asCharBuffer, asIntBuffer, asDoubleBuffer, asLongBuffer, etc. También provee vistas especiales, que si bien no modifican la estructura del ButeBuffer, le permiten mostrarse como de un tipo primitivo. Estas se implementan mediante las clases IntBuffer, LongBuffer, CharBuffer, DoubleBuffer, etc.

Acceso a bases de datos en Java

Han implementado en muchos casos una suerte de SQL multiplataforma que se puede hacer que funcione para los distintos modelos de bases de datos relacionales. Tal cosa ocurre en Java con JDBC (Java Database Connectivity).

A continuación se muestra un ejemplo sencillo del uso de JDBC para recorrer una tabla de una base de datos relacional:

68

Serialización y XML para almacenar objetos

El planteo general

La idea de que la persistencia deba existir en todas las clases a través de un método que deba conocer cómo, dónde y bajo qué sistema se debe guardar un objeto, se contrapone a la recomendación de separar las capas de acceso a datos y del modelo.

Una primera solución a este problema es la Serialización, que consiste en transformar un objeto en una cadena de bytes y almacenar ésta última. La clase sólo tendría el método que serializa el objeto, mientras que el almacenamiento quedaría en la capa de acceso a datos, con todas sus dependencias de plataformas. Multiplataforma. No es multilenguaje. Por supuesto, es necesario proveer también la función inversa a la serialización, a menudo denominada hidratación.

Cada plataforma debería conocer bien la forma en que se serializó el objeto de modo de poder rehidratarlo. En el caso de que usemos Java en ambos lados esto podría lograrse mediante la interfaz Serializable, como veremos enseguida pero

69

basta que alguna de las aplicaciones no esté escrita en Java para que sea necesario hacer una descripción bastante compleja de la forma de interpretar los datos serializados.

Una solución a este problema podría ser el uso de XML. Dada la estructura de XML, se podrían almacenar objetos compuestos sin mucho esfuerzo, garantizado la posibilidad de ser usados en varios lenguajes. Con APIs especiales en cada lenguaje: SAX, DOM, JDOM, dom4j. Multiplataforma y multilenguaje.

En este contexto, podríamos tener un método para cada clase que serialice objetos a fragmentos de código XML, y otro que haga lo inverso.

Por ejemplo, supongamos que la información de un empleado se guarda en un objeto cuyos atributos son número de legajo, apellido, nombre y fecha de nacimiento, siendo ésta última a su vez un objeto formado por día, mes y año. Su representación en XML podría ser:

Serialización en Java

Java tiene una interfaz Serializable, en el paquete java.io, de modo que toda clase que la implemente va a poder transformar sus objetos en cadenas de bytes y viceversa. Implementarla permite al compilador generar información de serialización. Evita el lanzamiento de NotSerializableException. Permite usar ObjectOutputStream y ObjectInputStream para leer y escribir.

Sólo faltará crear un objeto de clase OutputStream, envolverlo en un ObjectOutputStream e invocar al método writeObject.

70

Aquí se muestra la serialización de un objeto de clase Fraccion:

El proceso inverso también es muy simple. Dentro de la misma clase se podrá implementar un método hidratar.

public Fraccion hidratar (String archivo)throws IOException, ClassNotFoundException{

ObjectInputStream entrada = new ObjectInputStream(new FileInputStream(archivo));

return (Fraccion)entrada.readObject();}

Observamos que ni siquiera es necesario invocar a un constructor para hidratar un objeto (en realidad, el constructor es llamado por el método readObject). El hecho de tener que declarar que se lanzan las excepciones IOException y ClassNotFoundException se debe a que son arrojadas por los métodos de entrada y salida, y no se las puede ignorar.

En principio, esta solución no serializa los objetos contenidos, pero si estos objetos internos pertenecen a clases que implementan Serializable, la persistencia es isomórfica (si las clases ancestros también la implementan).

La interfaz Serializable asegura que se serialicen todos los atributos no declarados transient ni static, así como todos los objetos contenidos. Si se intenta serializar un objeto no serializable se va a obtener la excepción NotSerializableException.

Incluso se puede usar la reflexión de Java para determinar la clase del objeto que está almacenado en un archivo.

ObjectInputStream entrada = new ObjectInputStream(new FileInputStream(“datos.dat”));

Object o = entrada.readObject();if (o.getClass() == Fraccion.class)

71

Fraccion z = (Fraccion) o;

De esta forma se garantiza la persistencia polimórfica.

La serialización de Java asegura también que se copien los atributos privados. Sin embardo si no se querría serializar todo el objeto lo que se puede hacer es declarar un atributo con el modificador transient:

Private transient nombreUsuario;

Si se desea una funcionalidad más controlada por el programador se puede implementar la interfaz Externalizable, derivada Serializable, que expone dos métodos adicionales writeExternal y readExternal.

Un ejemplo de tarea para la que puede servir Externalizable es la de evitar que se serialicen ciertos atributos.

Persistencia y bases de datos

Persistencia y bases de datos relacionales

La persistencia no necesariamente se hace sobre bases de datos.

Una base de datos es una colección de datos interrelacionados, en el cual los datos se almacenan de modo que resulten independientes de los programas que los usan y existen métodos para incorporar o extraer datos.

Las hay de diversos tipos: en red (estructura de grato), jerárquicas (estructura de árbol), relacionales (tablas con registros y campos), de conocimiento (almacenan conocimientos para sistemas expertos) y demás. En el uso corporativo prácticamente la totalidad de las bases de datos son relacionales.

Las bases de datos relacionales se usan en un 90% pero no es orientada a objetos, por eso tiene problemas con la identidad de los objetos. No almacena objetos con atributos y comportamiento. Se construye una capa filtrante que haga el cambio de paradigma (capa de acceso a datos).

En primer momento, lo único que se hizo fue incorporar BLOBs (Binary Large Object, largas cadenas de bytes sin formato alguno) en las bases de datos relacionales, de modo de poder representar cualquier tipo de información como contenido, así como partes variantes de los objetos. El inconveniente principal es que los BLOBs no sirven para realizar búsquedas o indexaciones.

72

El siguiente paso fue la incorporación de interfaces (“front-ends”) orientadas a objetos a las bases de datos relacionales. Esta solución, que es aún la que más se utiliza, se la llama también de bases de datos relacionales basadas en objetos. En este caso hay un proceso que traduce los objetos al modelo relacional tradicional, que va a ser parte de la capa de acceso a datos. Por lo tanto los objetos deben ser interceptados antes de ser almacenados y traducidos de formato, y al recuperarlos se hace el procedimiento inverso.

Se trata de una solución mixta que mantiene el paradigma relacional en la capa de acceso a datos y el de objetos en la lógica de la aplicación. Entre sus ventajas está la simplicidad de implementación. Pero el software de traducción está disociado del universo del sistema. Esta solución deja al descubierto la dificultad de representar un objeto complejo en el modelo relacional.

Del modelo de objetos al modelo relacional

El problema a resolver, si se quiere implementar bases de datos relacionales basadas en objetos, es cómo se convierte un modelo de objetos y clases con herencia en un modelo relacional basado en tablas. Hay varias soluciones propuestas:

Definir una tabla por cada clase: es la solución más elemental y sencilla, pero puede derivar en problemas de escalabilidad al enriquecer la jerarquía de clases.

Definir una tabla por jerarquía de clases, de un modo que garantice lugar para todos los atributos de todas las clases de esa familia: es una solución mejor, pero se desperdicia mucho espacio de almacenamiento y tampoco es escalable.

Definir una tabla por cada clase ancestro superior, e ir definiendo tablas para las clases hijas de modo de agregarles solamente los atributos adicionales: el problema aquí es el procesamiento adicional que se requiere, uniendo tablas.

Bases de datos orientadas a objetos

Las bases de datos orientadas a objetos (BDOO) se desarrollaron para superar las limitaciones de las bases de datos relacionales y para ofrecer funcionalidades más avanzadas.

Tienen una definición imprecisa. Sin éxito comercial destacable. Muchas ni siquiera soportan herencia y polimorfismo.

73

Las bases de datos relacionales funcionan bien con grandes volúmenes de información, pero con una estructura regular de datos (registros similares) y con tipos de datos sencillos y predefinidos, por eso es que uno de los primeros caminos en la búsqueda de un cambio fue introducir los BLOBs. Además, el modelo relacional identifica cada objeto por el valor que contiene, mientas el modelo orientado a objetos puede perfectamente trabajar con dos objetos distintos cuyos estados sean los mismos (esta es la característica de la identidad de los objetos).

Se ha definido que una BDOO debe proveer como mínimo:

Persistencia. Control de acceso. Consultas basadas en valores de propiedades y no en la ubicación. Restricciones semánticas. Soporte de transacciones. Soporte del encapsulamiento: acceso a las propiedades internas a través de

una interfaz definida. Definir una identidad (única) de cada objeto que no dependa de su estado. Permitir las referencias entre objetos.

Algunas de las BDOO más conocidas son:

Gemstone Objective/DB ObjectStore Ontos O2 Itasca Matisse Versant Poet Objectivity

Visión J2EE:

Es la más audaz. Propone que todo EJB de tipo “CMP entity” mantenga su estado en un repositorio permanente sin que el programador se deba preocupar de ello. Con base de datos relacionales o no. Críticas: Restricciones sobre el modelo de clases, dificultades de implementación, bajo desempeño, aunque muy mejorada en Java EE 5.0.

Frameworks de persistencia:

74

Se ocupan de hacer un mapeo del mundo de los objetos al mundo relacional. “O/R-mapping”.

Generalmente trabajan con archivos XML que definen cómo se hace el mapeo.

Muchas soluciones comerciales muy buenas. Ahorran mucho trabajo. A veces me evitan desarrollar la DAL.

Casos: Hibernate, JDO, OJB, DataObjects.Si no son suficientes se pueden construir un propio framework.

75