Diseño de software con patrones (parte 3)

Con la colaboración de JavaHispano

Puedes encontrar la versión original en esta dirección

En el capítulo anterior comentamos lo que son los patrones de creacción y discutimos los patrones creador y factoría, que unidos con el patrón Singleton (también un patrón de creacción) del primer capítulo, se pueden considerar como los patrones de creacción básicos. En éste artículo terminaremos con los patrones de creacción comentando tres más (factoría abstracta, constructor y prototipo), aunque quizás de forma más superficial que los anteriores.

El patrón factoría abstracta (Abstract factory pattern, GoF).

Este patrón es muy sencillo si se ha entendido el patrón factoría. Como la palbra `abstacta` nos puede hacer suponer, este patrón lleva al de la factoría un punto más lejos en la idea de abstraer el código de creacción de obejtos del resto de la aplicación. ¿Cómo debemos entender esto?, pues una factoría abstracta es una clase (factoría como las del artículo anterior) pero que los objetos que devuelve son a su vez factorías. Por este motivo, para que sea efectiva, estas factorías que devuelve, deben ser de la misma familia (es decir, tener antecesores comúnes), como ocurría con las factorías normales.

Como todo se entiende mejuor con un ejemplo, pasaremos a un caso claro de la utilización de factorías: los distintos 'look&feel' de Java.

En Java podemos tener el mismo programa con distintos aspectos cambiando solo una línea de código, la que se encarga de indicar el look&feel (en estos momentos los estándar son Metal, Motif y Windows) que queremos utilizar. Esto es posible gracias a que la clase UIManager de Java es una factoría abstracta que genera factorías de componentes visuales. Así , cunado creamos un componente visual para nuestra interfaz de usuario, Java acude al look&feel seleccionado, que como hemos dicho es una fctoría, y le pide el componente escogido con el aspecto que corresponda.

Supongo que entendiendo el patrón de la factoría un ejemplo de código sobre la factoría abstracta es innecesario, por lo que me lo saltaré, pero si ha alguién no le ha quedado claro ya sabe donde tiene que preguntar sus dudas.

La ventaja que obtenemos del uso de este patrón esta en la posibilidad de ampliar la funcionalidad de la aplicación con solo añadir una nueva familia (lease factoría) de elementos y un mínimo retoque de la aplicación (para llevar a cabo la integración de esa familia). Aunque esto puede dejar de ser 'sencillo' si, como en el caso de las familias de componetes visuales, una familia se compone de cientos de elementos.

El patrón constructor (Builder pattern, GoF).

El patrón constructor sigue la misma idea que el patrón factoría, pero no se encarga simplemente de devolver una clase, si no de construir una composición de objetos entera, por compleja o sencilla que sea. El caso más habitual es el de construir una interfaz de usuario, pero no se limita únicamente a componentes visuales.

Un ejemplo podría ser , siguiendo con el fútbol del capítulo anterior, la pantalla que muestra los datos de un jugador. Podríamos hacerlo de forma que mostrase la misma información para todos los jugadores, pero a nadie se le escapa que no es lo mismo un portero que un delantero, al menos por ningún portero se pagan 10.000 millones ;-). Ya sea porque le dejamos al usuario configurarlo, o porque nosotros lo hemos codificado así, resulta poco útil, al menos normalmente, saber cuantos goles a metido un portero (si, no soy un fanático futbolero pero conozco a Chilavert ;-) ), lo mismo que cuantas paradas ha hecho un delantero. Lo habitual es que esos datos sean 0 permanentemente, es decir, no aportan nada.

Ok, pasemos a "implementarlo". Lo digo entre comillas porque no lo haré entero, claro esta. Primero supongamos la clase Jugador con los datos básicos, como nombre, edad, equipo, etc.

public class Jugador
{
  private String nombre;
  private int edad;
  private Equipo equipo;
  . . .
}

Y ahora supongamos las subclases de Jugador, aqui por ejemplo, Delantero (con el resto de tipos de jugadores sería lo mismo), con toda la información que queramos sobre su rendmiento:

public class Delantero extends Jugador
{
  private int goles = 0;
  private int remates = 0;
  . . .
}

Estas son las "clases del negocio". Las clases que tienen la información que interesa en nuestra aplicación. Por supuesto faltan muchas más, pero en fin, yo no estoy haciendo un clónico opensource del PCFútbol ;-).

Ahora pasemos a las clases que manejan la interface de usuario, al menos en la parte que muestra la información de los jugadores.

Primero definiremos una clase abstracta que actue como base de las demas, y que realice las tareas comúnes, como sería mostrar el nombre, la edad, y demas información contenida en la clase Jugador. Aqui también podemos poner los controles que necesitemos para mostrar más de tallaes, o cerrar la ventana, o, si como he dicho, estamos haciendo el clónico del PCFútbol, para poner los botones de "despedir" o "hacer oferta" para gestionar nuestro equipo.

abstract class PanelJugador
{
  private JPanel panel;

  public PanelJugador (Jugador jug)
  {
     /*
       hacer todo lo que necesitemos para crear la IU,
       paneles, botones, etiquetas, etc.
     */    
  }

  abstract public JPanel getIU ();
}

Ahora pasemos a hacer las tareas exclusivas del delantero, es decir, el "constructor" (el constructor de su interface, no de su clase) del delantero:

public class PanelDelantero
{
  public PanelDelantero (Deltantero jug)
  {
     super (jug);
     /*
       hacer despues las tareas especificas del delantero.
     */    
  }

  public JPanel getIU ()
  {
     return (panel);
  }
}

Por último nos queda crear una paqueña factoría que nos devuelva el panel adecuado al tipo de Jugador que estemos tratando:

public class FactoriaPanelesJugadores
{
  public static PanelJugador getPanelJugador (Jugador jug)
  {
    PanelJugador pj;
    if (jug instanceof Delantero)
      pj = new PanelDelantero (jug);
    if (jug instanceof Defensa)
      pj = new PanelDefensa (jug);
    if (jug instanceof Centrocampista)
      pj = new PanelCentrocampista (jug);
    if (jug instanceof Portero)
      pj = new PanelPortero (jug);
     
    return (pj);
  }
}

Ya lo tenemos hecho, ahora siempre que queramos mostrar la información de un jugador, detallada según su posición, lo que tenemos que hacer es simplemente:

PanelJugador pj = FactoriaPanelesJugadores.getPanelJugador (jugador);

y colocar el panel que obtenemos donde queramos (una ventana, un cuadro de diálogo, una ventana interna, etc) por medio de:

ventana.add (pj.getIU());

Como he dicho antes, tampoco tenemos que tener clases separadas para cada tipo de jugador, puede ser que tengamos una clase Jugador con todos los atributos, y luego, que una de las opciones del programa fuera dejarle al usuario definir que datos mostrar de cada tipo. En ese caso, el dato diferencial sería la posición del jugador, que indicaría que instancia tomar de la clase que contuviera la información sobre que datos mostrar para esa posición.

Este ejemplo es ampliable, por ejemplo, usando nuevos parametros que indiquen si estamos en modo de edición o no, para determinar si mostramos botones para guardar los datos o cancelar los cambios, o para sber si mostramos la información en etiquetas o cajas de texto.

A su vez, este patrón es aplicable no sólo a distintas clases de datos, como tipo de jugador o tipo de vehículo (no es lo mismo un coche que una camión), si no que también puede aplicarse a una misma clase en función de la cantidad de información que tengamos. Por ejemplo, si tenemos que seleccionar varios valores de una lista de elementos, si son pocos elemetnos, podemos ofrecer una serie de "checkbox", pero si son muchos mejor una lista con posibilidad de multiselección.

La idea principal de este patrón es la de separar lo más posible los datos de su representación, y para eso Java es un lenguaje más que apropiado. Podemos variar la represetación de los objetos según nos interese, sin tener que tocar las clases que almacenan los datos.

Ademas, cada constructor es independiente de los demas, así que un cambio en la represnetción de los porteros, o de la clase base Jugador, no afecta en nada a las demas.

El patrón prototipo (Prototype pattern, GoF)

Este patrón se usa en los casos en los que crear una instancia de una clase sea un proceso muy complejo y requiera mucho tiempo. Lo que hace es crear una instancia original, y, cuando necesitemos otra, en lugar de volver a crearla, copiar esa original y modificarla de acuerdo a las indicaciones de la nueva petición.

Un ejemplo... , podría ser un listado de una base de datos, un listado muy muy largo, ¿qué tal con 1.000 objetos?. Bien, puede courrir que queramos mostrar ese listado en dos ventanas, ordenado por distintos criterios, para comprobar algún detalle. ¿Qué no sale mejor?, ¿leer de nuevo la base de datos entera o hacer una copia de la primera lectura y ordenadrla?. Obviamente lo más adecuado es la segunda.

Ahora no me voy a meter como ordenadr los datos en Java, pero os puedo decir que si alguna vez lo teneis que hacer os mireis la clase Comparator y el interface Comparable. Solo diré como hacer la copia de los datos.

La primera opción es implementar el interface Clonable y usarlo según nuestra necesidades, quizás sobreescribiendolo para que devuelva una instancia de nuestra clase que contiene todos los elementos en lugar de una de la clase Object.

Pero hay que tener en cuenta que esto no siempre es aceptable, porque Clone hace una copia de las referencias, eso si, pero referencia a los mismos objetos en memoria, por lo que habrá que realmente reescribirla para que haga una copia real de los objetos, la forma más rápida sería haciendolo por medio de el interface Serializable, pero eso no tiene mucho que ver con los patrones y si con "Java practico", así que tendreís que esperar a otro artículo sobre el asunto. Yo, hoy, lo dejo aquí.

Conclusiones

Con esto hemos acabado con los patrones de creacción fundamentales, no son los únicos y quizás no los mejores, pero recordar que los patrones solo son "soluciones aceptadas como buenas a problemas comúnes", una forma de hacer software los más reusable, adaptable, ampliable, y consistente posible, pero como en todo, para gustos los colores.

En el próximo artículo, empezaremos con otro tipo de patrones, los estructurales. Espero que llegue pronto, aunque quizás me dedique más a escribir sobre Java y menos sobre estas cosas que no se si interesan.

Articulos anteriores

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP