• No se han encontrado resultados

Usar un mapa en el sistema Soporte Técnico

In document Programacion Orientada a Objetos Con Java (página 185-193)

Comportamiento más sofisticado

5.5 Paquetes y la sentencia import

5.6.3 Usar un mapa en el sistema Soporte Técnico

En el sistema Soporte Técnico podemos hacer un buen uso de un mapa usando pala- bras conocidas como llaves y las respuestas asociadas como valores. El Código 5.4 muestra un ejemplo en el que se crea un HashMapde nombre mapaDeRespuestasy

se ingresan tres entradas en él. Por ejemplo, la palabra «lento» se asocia con el texto:

«Me parece que esto tiene que ver con su hardware. Actualizar su procesador podría resolver todos estos problemas. ¿Ha tenido algún inconveniente con nuestro software?»

Ahora, cuando alguien ingrese una pregunta que contenga la palabra «lento» podremos buscar e imprimir esta respuesta. Observe que la cadena de respuesta en el código ocupa varias líneas pero concatenadas con el operador +, de modo que el valor que entra en el HashMapes de una sola línea.

5.6 Usar mapas para las asociaciones 149

private HashMap mapaDeRespuestas<String, String>;

public Contestador() {

mapaDeRespuestas = new HashMap<String, String>(); rellenarMapaDeRespuestas(); Código 5.4 Asociación de palabras seleccionadas con posibles respuestas 05 CAPITULO 05 8/1/07 17:55 Página 149

Un primer intento de escribir un método para generar las respuestas podría ser similar

al método generarRespuesta que ofrecemos a continuación. En este punto y para

simplificar las cosas por el momento, asumimos que el usuario ingresa solamente una palabra, por ejemplo «lento».

public String generarRespuesta(String palabra)

{

String respuesta = mapaDeRespuestas.get(palabra); if (respuesta != null) {

return respuesta; }

else {

// si llega acá es porque la palabra no fue reconocida

150 Capítulo 5  Comportamiento más sofisticado

}

/**

* Ingresa todas las palabras llave conocidas y sus * respuestas asociadas, en nuestro mapa de

respuestas. */

private void rellenarMapaDeRespuestas() {

mapaDeRespuestas.put("lento",

"Me parece que esto tiene que ver con su hardware. \n" +

"Actualizar su procesador podría resolver \n" +

"todos estos problemas. \n" +

"¿Ha tenido algún inconveniente con nuestro software?");

mapaDeRespuestas.put("problema",

"Bueno, Ud. sabe, todos los programas tiene \n" +

"algún defecto. \n" + "Pero nuestros ingenieros están trabajando \n" +

"duro para solucionarlos. \n" +

"¿Puede describir el problema más detalladamente? \n");

mapaDeRespuestas.put("caro",

"El precio de nuestro producto es muy competitivo. \n" +

"Realmente, ¿Ha visto y comparado todas nuestras \n" +

"características"); } Código 5.4 (continuación) Asociación de palabras seleccionadas con posibles respuestas

// En este caso, tomamos una de nuestras

respuestas por defecto

return tomarRespuestaPorDefecto(); }

}

En este fragmento de código buscamos la palabra ingresada por el usuario en nuestro mapa de respuestas. Si encontramos una entrada que contenga la palabra ingresada por el usuario, la usamos para obtener la respuesta asociada. Si no encontramos una entrada para esa palabra, invocamos al método tomarRespuestaPorDefecto. Este método

puede contener ahora el código de nuestra versión anterior de generarRespuesta que genera una respuesta aleatoriamente a partir de la lista de respuestas por defecto (tal como muestra el Código 5.3). La nueva lógica consiste en recuperar una respuesta adecuada si reconocemos la palabra o una respuesta aleatoria de nuestra lista de res- puestas por defecto si no reconocemos la palabra ingresada.

Ejercicio 5.31Implemente las modificaciones de las que hablamos aquí en su propia versión del sistema de Soporte Técnico. Pruébelo para ver si funciona correctamente.

Este enfoque de asociar palabras llave con respuestas funciona bastante bien siempre y cuando el usuario no ingrese preguntas completas, es decir, funciona bien sólo cuando ingrese una sola palabra. La mejora final para completar la aplicación consiste en dejar que el usuario ingrese nuevamente preguntas completas y luego obtener respuestas que coincidan si reconocemos cualquiera de las palabras que contiene la pregunta. Esta situación posiciona el problema en reconocer las palabras llave en la oración ingre- sada por el usuario. En la versión actual, el ingreso del usuario es devuelto por el

LectorDeEntrada como una única cadena. Ahora queremos modificar este hecho para construir una nueva versión en la que el LectorDeEntrada devuelva la entrada del usuario como un conjunto de palabras. Técnicamente, la entrada será un conjunto de cadenas en el que cada cadena del conjunto representa una sola de las palabras ingresadas por el usuario.

Si logramos hacerlo, entonces podemos pasar el conjunto completo de palabras de la entrada del usuario al Contestador,que evaluará cada palabra del conjunto para ver si es reconocida y tiene una respuesta asociada.

Para implementar esta mejora en Java, necesitamos saber dos cosas: cómo dividir una única cadena en las varias palabras que contiene y cómo usar conjuntos. Estos son los puntos que trataremos en las próximas dos secciones.

5.7

Usar conjuntos

La biblioteca estándar de Java incluye diferentes variantes de conjuntos, implementados en clases diferentes. La clase que usaremos se denomina HashSet.

Ejercicio 5.32¿Cuáles son las similitudes y las diferencias entre un HashSet y un ArrayList? Utilice las descripciones de Set, HashSet, List y Array- List que están en la documentación de la biblioteca para averiguarlo, dado que un HashSet es un caso especial de Set y un ArrayList es un caso

especial de List.

5.7 Usar conjuntos 151

Los dos tipos de funcionalidad que necesitamos de un conjunto son: ingresar elementos en él y más tarde, recuperar estos elementos. Afortunadamente, estas tareas no tienen demasiada dificultad para nosotros. Considere el siguiente fragmento de código:

import java.util.HashSet;

import java.util.Iterator; ...

HashSet<String> miConjunto = new HashSet<String>();

miConjunto.add("uno"); miConjunto.add("dos"); miConjunto.add("tres");

Compare este código con las sentencias que necesitamos para entrar elementos en un ArrayList. No hay prácticamente ninguna diferencia, excepto que esta vez creamos un

HashSeten lugar de un ArrayList. Ahora veamos un recorrido por todos los elementos:

for(String : miConjunto) { Hacer algo con cada elemento

}

Nuevamente estas sentencias son las mismas que las que usamos para recorrer un

ArrayList en el Capítulo 4.

Brevemente: los diferentes tipos de colecciones de Java se usan de manera muy similar. Una vez que comprendió cómo usar una de ellas, puede usarlas todas. Las diferencias reales residen en el comportamiento de cada colección. Por ejemplo, una lista contiene todos los elementos ingresados en el orden deseado, provee acceso a sus elementos a través de un índice y puede contener el mismo elemento varias veces. Por otro lado, un conjunto no mantiene un orden específico (el iterador puede devolver los elementos en diferente orden del que fueron ingresados) y asegura que cada elemento en el con- junto está una única vez. En un conjunto, el ingresar un elemento por segunda vez simplemente no tiene ningún efecto.

152 Capítulo 5  Comportamiento más sofisticado

Concepto

Un conjunto es una

colección que almacena cada elemento individual una sola vez como máximo. No mantiene un orden específico.

List, Map y Set Es tentador asumir que se puede usar un HashSet de manera similar a un HashMap. En realidad, tal como lo ilustramos, la forma de usar un

HashSet es más parecida a la forma de usar un ArrayList.Cuando tratamos de comprender la forma en que se usan las diferentes clases de colecciones, la segunda parte del nombre es la mejor indicación de los datos que almacenan, y la primera palabra describe la forma en que se almacenan. Generalmente estamos más interesados en el «qué» (la segunda parte) antes que en el «cómo». De modo que un TreeSet debiera usarse de manera similar a un HashSet, mientras que un TreeMap debiera usarse de manera similar a un HashMap.

5.8

Dividir cadenas

Ahora que hemos visto cómo usar un conjunto, podemos investigar cómo podemos dividir una cadena de entrada en palabras separadas para almacenarlas en un conjunto de palabras. La solución se muestra en una nueva versión del método getEntrada de

En este código, además de usar un HashSettambién utilizamos el método splitde

la clase String, que está definido en la biblioteca estándar de Java.

El método splitpuede dividir una cadena en distintas subcadenas y las devuelve en un arreglo de cadenas. El parámetro del método splitestablece la clase de caracteres de la cadena original que producirá la división en palabras. Hemos determinado que queremos dividir nuestra cadena mediante cada carácter espacio en blanco.

Las restantes líneas de código crean un HashSety copian las palabras desde el arreglo al conjunto, antes de retornar el conjunto3.

Ejercicio 5.33 El método split es más poderoso de lo que parece a partir de nuestro ejemplo. ¿Cómo puede definir exactamente cómo se dividirá la cadena? Dé algunos ejemplos.

5.8 Dividir cadenas 153

/**

* Lee una línea de texto desde la entrada estándar (la terminal de

* texto) y la retorna como un conjunto de palabras. *

* @return Un conjunto de cadenas en el que cada String es una de las

* palabras que escribió el usuario. */

public HashSet<String> getEntrada() {

System.out.print("> "); // imprime el prompt

String linea =

lector.lineaSiguiente().trim().toLowerCase();

String[] arregloDePalabras = linea.split(" ");

// agrega las palabras del arreglo en el hashset

HashSet<String> palabras = new HashSet<String>(); for (String palabra : arregloDePalabras) {

palabras.add(palabra); } return palabras; } Código 5.5 El método getEntrada devuelve un conjunto de palabras

3 Existe una manera más elegante y breve de hacer lo mismo. Podríamos escribir

HashSet<String> palabras = new HashSet<String>(Arrays.asList(arregloDePalabras));

para reemplazar las cuatro líneas de código. Esta manera usa la clase Arraysde la biblioteca estándar y un método estático (también conocido como método de clase) que aún no hemos

tratado en este libro. Si tiene curiosidad por este tema, recurra a la Sección 7.15.1 donde hablamos sobre los métodos de clase e intente usar esta versión.

Ejercicio 5.34Si quiere dividir una cadena en subcadenas, ya sea mediante cada carácter espacio en blanco o cada carácter de tabulación ¿Cómo podría invocar al método split? ¿Cómo se podría hacer si las palabras están sepa- radas mediante el carácter dos puntos (:)?

Ejercicio 5.35 ¿Cuál es la diferencia de resultados al devolver las palabras en un HashSet en comparación con devolverlas en un ArrayList?

Ejercicio 5.36 Si existe más de un espacio en blanco entre dos palabras, por ejemplo, dos o tres espacios ¿qué ocurre?, ¿hay algún problema?

Ejercicio 5.37 Desafío.Lea la nota al pie sobre el método Arrays.asList.

Busque y lea las secciones de este libro que tratan sobre variables de clase y métodos de clase. Explique con sus propias palabras cómo funcionan.

¿Cuáles son los ejemplos de los otros métodos que proporciona la clase

Arrays?

Cree una clase de nombre PruebaOrdenamiento.Cree en ella un método que acepte como parámetro un arreglo de valores enteros e imprima en la terminal los elementos ordenados (de menor a mayor).

5.9

Terminar el sistema de Soporte Técnico

Para poner en acción las modificaciones que realizamos, tenemos que ajustar las clases

SistemaDeSoporte y Contestador de modo que trabajen correctamente con un

conjunto de palabras en lugar de con una sola cadena. El Código 5.6 muestra la nueva versión del método iniciar de la clase SistemaDeSoporteque no presenta dema- siados cambios. Los cambios son:

 La variable entrada, que recibe el resultado desde lector.getEntrada(), ahora

es de tipo HashSet.

 El control para finalizar la aplicación se hace mediante el método contains de la

clase HashSeten lugar de hacerlo mediante un método de la clase String. (Busque

este método en la documentación.)

 La clase HashSetdebe ser importada usando una sentencia import (que aquí no

se muestra).

154 Capítulo 5  Comportamiento más sofisticado

public void iniciar()

{

boolean terminado = false; imprimirBienvenida(); while(!terminado) { HashSet<String> entrada = (lector.getEntrada(); if(entrada.contains("bye")) { terminado = true; Código 5.6

Versión final del método iniciar

Finalmente, tenemos que ampliar el método generarRespuesta de la clase Con- testadorpara que acepte un conjunto de palabras como parámetro. Luego, debe reco- rrer este conjunto y controlar cada una de las palabras en nuestro mapa de palabras conocidas. Si reconoce alguna de las palabras, retorna inmediatamente la respuesta aso- ciada. Si no puede reconocer ninguna de las palabras, tomaremos como antes, una de las respuestas por defecto. El Código 5.7 muestra la solución.

5.9 Terminar el sistema de Soporte Técnico 155

} else { String respuesta = contestador.generarRespuesta(entrada); System.out.println(respuesta); } } imprimirDespedida(); } Código 5.6 (continuación)

Versión final del método iniciar

public String generarRespuesta(HashSet<String> palabras) {

Iterator<String> it = palabras.iterator(); while (it.hasNext()) {

String palabra = (String) it.next(); String respuesta = mapaDeRespuestas.get(palabra); if (respuesta != null) { return respuesta; } }

// si llega acá es porque la palabra no fue reconocida

// En este caso, tomamos una de nuestras respuestas por defecto

return getRespuestaPorDefecto(); }

Código 5.7

Versión final del método

generarRespuesta

Esta es la última modificación a esta aplicación que tratamos en este capítulo. La solu- ción en el proyecto soporte-tecnico-completo contiene todos estos cambios; también

contiene más palabras asociadas con respuestas que las que se presentan en este capí- tulo.

Por supuesto que es posible realizar muchas mejoras a esta aplicación, pero no las dis- cutiremos aquí sino que las sugerimos como ejercicios que quedan en manos del lector, algunos de los cuales son ejercicios desafiantes de programación.

Ejercicio 5.38 Implemente las modificaciones finales de las que hablamos anteriormente, en su propia versión del programa.

Ejercicio 5.39 Agregue en su aplicación más pares de palabras y respuestas al mapa. Puede copiar alguna de las que ofrece la solución y agregarlas por sus propios medios.

Ejercicio 5.40 A veces, dos palabras (o variantes de una palabra) pueden vincularse con la misma respuesta. Trabaje con esta idea vinculando sinónimos o expresiones relacionadas con la misma cadena, de modo que no necesite tener varias entradas en el mapa para la misma respuesta.

Ejercicio 5.41 Identifique en el ingreso del usuario varias palabras que coin- cidan con las almacenadas en el mapa y responda con la respuesta que más se ajuste.

Ejercicio 5.42Para el caso en que no se reconoció ninguna de las palabras, utilice otras palabras del ingreso del usuario para filtrar mejor la respuesta por defecto: por ejemplo, las palabras «por qué», «cómo», «quien».

5.10

Escribir documentación de clase

Cuando se trabaja sobre proyectos es importante escribir la documentación para sus clases, a medida que se desarrolla el código. Es muy común que los programadores no se tomen el trabajo de documentar seriamente y de manera suficiente sus programas y es más frecuente aún, que más tarde este defecto genere serios problemas.

Si no suministra suficiente documentación será muy difícil que otros programadores logren comprender sus clases (¡O que usted mismo no las comprenda pasado un tiempo!). Típicamente, lo que tiene que hacer en estos casos, es leer la implementa- ción de la clase e imaginar qué hace. Esta manera puede funcionar con proyectos pequeños de estudio, pero crea serios problemas en los proyectos reales.

No es poco frecuente que las aplicaciones comerciales contengan cientos de miles de líneas de código agrupadas en varios miles de clases. ¡Imagine si tiene que leer todo este código para comprender cómo funciona una aplicación! Parece que jamás tendría éxito.

Cuando usamos las clases de la biblioteca de Java tales como HashSeto Random, nos

hemos apoyado exclusivamente en su documentación, para averiguar cómo usarlas. Nunca hemos mirado la implementación de esas clases. Este camino funcionó porque estas clases están suficientemente bien documentadas (aunque, por cierto, esta docu- mentación podría mejorarse). Nuestra tarea hubiera resultado más complicada si hubié- ramos tenido que leer las implementaciones de dichas clases antes de usarlas. Es típico que en un equipo de desarrollo de software, la implementación de las clases sea compartida entre muchos programadores. Mientras que uno de los programadores es el responsable de implementar la clase SoporteDeSistema de nuestro último

ejemplo, otros deben implementar el LectorDeEntrada, de modo que el primer pro- gramador tendrá que invocar métodos de las otras clases mientras se dedica a su propia clase.

El mismo argumento que damos para las clases de biblioteca es válido para las clases que escribimos: si podemos usar las clases sin tener que leer y comprender su imple- mentación completa, nuestra tarea se vuelve más fácil. Tal como en las clases de biblio- 156 Capítulo 5  Comportamiento más sofisticado

Concepto

La documentación de una clase debiera ser suficientemente detallada como para que otros

programadores puedan usarla sin tener que leer su implementación.

teca, queremos ver solamente la interfaz pública de la clase en lugar de su implemen- tación. En consecuencia, es muy importante escribir una buena documentación para nuestras propias clases.

El sistema Java incluye una herramienta denominada javadoc que se puede utilizar

para generar la interfaz que describa nuestros archivos fuente. La documentación de la biblioteca estándar que hemos usado, por ejemplo, fue creada a partir de código fuente de sus clases mediante el javadoc.

In document Programacion Orientada a Objetos Con Java (página 185-193)