Otro punto interesante de la API para ver cómo aplicar orientación a objetos y patrones son los InputStream y OutputStream. Me centro en los primeros porque los segundos son iguales.
Un InputStream es la clase -en realidad Interface- que nos proporciona los métodos para leer bytes de algún sitio -un fichero, un socket, otro proceso que esté corriendo, etc-. Sin embargo, sólo tiene métodos para leer bytes. ¿Por qué?
De un fichero, socket, etc podemos querer leer muchas cosas: bytes, enteros, cadenas de caracteres, clases java completas, etc. Hacer todos los posibles métodos daría una Interface muy grande y lo más seguro es que el programador siempre necesite o invente un nuevo tipo de dato que no está contemplado.
La solución que aplica Java es hacer distintas clases -InputStreamReader, BufferedReader, ObjectInputStream, DataInputStream, etc- de forma que cada una de ellas cubre una de estas necesidades. Por ejemplo, InputStreamReader permite leer caracteres en vez de bytes, BufferedReader permite leer líneas completas, ObjectInputStream permite leer objetos java, DataInputStream leer enteros, floats, etc.
¿Cómo se construyen estos objetos?. Pues "metiendo unos dentro de otros". Por ejemplo, la base es InputStream que lee bytes. Si queremos leer caracteres, construimos un InputStreamReader metiendo dentro, en el constructor, el InputStream que tengamos:
InputStream is = ….;
InputStreamReader isr = new InputStreamReader (is);
Si queremos leer líneas enteras, necesitamos primero algo que sepa leer caracteres, por lo que tenemos que "anidar" otro objeto más
InputStream is = …;
InputStreamReader isr = new InputStreamReader(is);
BufferedInputStream bis = new BufferedInputStream (isr);
El resumen de esto es claro. Cada clase sabe hacer una sola cosa -leer un tipo de dato- y se apoya para hacerlo en otra que sepa hacer algo más básico. La que lee cadenas de caracteres se apoya en la que lee caracteres para construir la cadena y esta en la que lee bytes para leer los caracteres.
Esto, en el mundo de los patrones, se conoce como patrón decorador y consiste en lo mencionado, anidar clases similares unas dentro de otras, como si fueran capas de cebolla, de forma que las más externas añaden funcionalidad a las capas internas. Este mecanismo permite construirnos nuestra cebolla a nuestro gusto, con las funcionalidades/capas que queramos, en vez de cargar en una sola clase con todas. Es más, podemos hacer nuestra propia capa de cebolla, siguiendo las reglas, de forma que se integre perfectamente con todas las demás capas de cebolla. La nuestra podría ir dentro de otras y debería poder acoger a otras.
Otro detalle más, antes de que se me olvide. En este mundo de los InputStream, no sólo hay capas de cebolla para leer distintos tipos de datos. La clase BufferedInputStream es otra capa más que podemos añadir o no. Nos proporciona un Buffer que hace más eficiente las lecturas. Si nosotros leemos un solo byte, esta capa se encarga de leer y almacenar un grupo grande de bytes, para aprovechar el acceso a disco. En la lectura del siguiente byte, no accederá a disco, puesto que ya lo tiene guardado, ahorrándonos otro acceso más.
Con un poco de imaginación podemos aplicar este patrón en muchas ocasiones. Por ejemplo, si tienes un TableModel que es reflejo de una tabla de base de datos, puedes hacerte una "capa de cebolla" que sea TableModelBD. Debe implementar los métodos de TableModel, para comportarse como él, admitir dentro un TableModel con los datos y redefinir los métodos de datos -setValueAt(), getValueAt(), getRowCount(), etc- para que hagan las consultas pertinentes a base de datos y metan los resultados en el TableModel interno. De esta forma, podemos meter indistintamente en un JTable nuestro TableModelBD que tiene dentro un TableModel normal, o bien directamente el TableModel normal.
Aunque no forma parte de la API de java, otra aplicación de este patrón decorador que he visto implementada por ahí es la siguiente. A veces puedes querer que los datos de un JTable aparezcan ordenados por columnas, que determinadas filas se "filtren" y no se vean, aunque sí estén en el modelo de datos, o incluso que algunas columnas no se vean. Una forma elegante de resolver este problema, además de configurable, consiste en aplicar el patrón decorador, haciendo capas de cebolla sobre el TableModel.
Por ejemplo, podemos hacer un TableSorter, un TableFilter y un TableColumnFilter. Todos ellos deben implementar TableModel y todos ellos deben admitir en su interior otro TableModel. TableSorter, por ejemplo, en getRowCount() devolvería el número de filas que tiene su TableModel interno. Pero cuando le pedimos el primer elemento -el de la fila 1-, debe buscar entre las filas del TableModel interno a ver cual es la primera según el orden que se establezca -alfabético, por ejemplo- y devolver como primera fila esa. El TableFilter, por ejemplo, en getRowCount() debería ver cuantas filas de su TableModel interno pasan el filtro y devolver ese número de filas. El TableColumnFilter, en getColumnCount() devolvería sólo el número de columnas que se quieren visibles.
De esta forma tenemos tres capas de cebolla que podemos añadir a nuestro gusto sobre un TableModel que hace de núcleo central de la cebolla. Podemos fácilmente tener una tabla normal, una tabla ordenada, una tabla filtrada y ordenada, una tabla filtrada que sólo muestre determinadas columnas y cualquier combinación que se nos ocurra.
Por supuesto, para hacer esto más configurable, debemos aplicar un patrón estrategia para decidir cómo ordenar o qué elementos pasan el filtro. Si no lo hacemos así, deberíamos hacer un TableSorter específico para cada tipo de ordenación que queramos -alfabético, numérico, etc- o bien un super mega TableSorter que sepa ordenar muchos tipos distintos de datos, pero que seguro que no ordena justo el que necesitamos. Nuestro TableSorter debería admitir un Comparator y usarlo para saber si un dato está delante o detrás de otro. Para el TableFilter deberíamos inventar una InterfaceFiltro, con un método boolean pasaFiltro(Object) para saber si un dato pasa o no el filtro.
En fin, una forma elegante de filtrar y ordenar datos en un JTable, muy configurable y ampliable.
Volviendo al InputStream, otro tema interesante es la forma de obtenerlo. Hemos dicho que se pueden leer bytes de un fichero, de un socket, de otro proceso que está ejecutándose, etc. El fichero no cumple lo que voy a mencionar, pero los sockets y procesos sí. Las clases Socket y Proccess sólo saben lo que tienen que saber, es decir, abrir el socket o mantener la información del proceso. No tienen métodos para leer nada de ellos. ¿Por qué?. Nuevamente el reparto de responsabilidades, de forma que cada clase sólo sepa hacer una cosa. ¿Cómo leemos entonces los datos?. Sencillo, les pedimos a la clase Socket o Process el InputStream correspondiente. Luego, en función del tipo de dato que llegue por el Socket o Process, nos construimos las capas de la cebolla que necesitemos.
Nuevamente vemos que cada clase hace lo que tiene que hacer y es una sola cosa -establecer conexión con socket- o leer bytes -el InputStream- y que no nos da más funcionalidad de la estrictamente necesaria -sólo podemos leer bytes-. Sin embargo, sí tenemos la posibilidad de configurarlo todo después a nuestro gusto -las capas de cebolla-.
En resumen, esta es una buena forma de no hacer clases gigantes con montones de métodos, en la que seguramente siempre faltará justo el método que nos hace falta. Es un mecanismo perfectamente extensible, que nos permite crear nuestras propias capas de cebolla que se integrarían perfectamente con las demás.
May 6th, 2007 at 2:36 pm
Hola:
Otra vez sin necesidad de que tenga que leer mucha documentación(aunque ahora lo voy a hacer para consolidar conocimientos pero ya tengo la base eso es lo importante) me acabas de facilitar el aprendizaje de un nuevo patrón(Decorador). Por ahí leí de que tienes una forma de explicar las cosas complicadas de una manera facil y pues apoyo esa afirmación. Felicidades sigue asi!!!
May 6th, 2007 at 7:37 pm
Hola:
Gracias. Para el III tengo pensado el patrón composición (composite, no sé muy bien cómo traducirlo).
Ese comentario de “explicar las cosas complicadas de manera fácil” ya lo he oído en alguna ocasión. Es de un amiguete de toda la vida que me hace un poco la pelota
También me decía que “soy capaz de convencer a cualquiera de cualquier cosa”. Le dije que iba a convencerle de que no era así, con lo que le metí en un pequeño aprieto…
Se bueno.