1.9.3. Fase 2: ¿Cómo podemos construirlo?

En esta fase debe crear un diseño que describa cómo son las clases y cómo interactuarán. Una técnica excelente para determinar clases e interacciones es la tarjeta Clase-Responsabilidad-Colaboración (Class-Responsibility-Collaboration) o CRC. Parte del valor de esta herramienta es que es de muy baja tecnología: se empieza con un montón de tarjetas en blanco de 3 por 5 pulgadas, y se escribe sobre ellas. Cada tarjeta representa una única clase, y en ella se escribe:

  1. El nombre de la clase. Es importante que el nombre refleje la esencia de lo que hace la clase, de modo que tenga sentido con un simple vistazo.

  2. Las «responsabilidades» de la clase: qué debe hacer. Normalmente se puede resumir simplemente enunciando los nombres de las funciones miembro o métodos (ya que en un buen diseño esos nombres deberían ser descriptivos), pero eso no excluye otras notas. Si necesita dar el primer impulso al proceso, mire el problema desde un punto de vista de programador perezoso: ¿Qué objetos quiere que aparezcan por arte de magia para resolver su problema?

  3. Las «colaboraciones» de la clase: ¿qué otras clases interactúan con ésta? «Interacción» es un término amplio a propósito; puede significar agregación o simplemente que existe algún otro objeto que prestará servicios a un objeto de la clase. Las colaboraciones deberían considerar también la audiencia para esta clase. Por ejemplo, si crea una clase Petardo, ¿quién va a observarlo, un Químico o un Espectador? El primero puede querer saber qué componentes químicos se han usado en su construcción, y el segundo responderá a los colores y figuras que aparezcan cuando explote.

Puede pensar que las tarjetas deberían ser más grandes por toda la información que le gustaría poner en ellas, pero son pequeñas a propósito, no sólo para que las clases se mantengan pequeñas sino también para evitar tener que manejar demasiados detalles demasiado pronto. Si no puede apuntar todo lo que necesita saber sobre una clase en una tarjeta pequeña, la clase es demasiado compleja (o está poniendo demasiados detalles, o debería crear más de una clase). La clase ideal se entiende con un vistazo. La idea de las tarjetas CRC es ayudarle a obtener una primera versión del diseño de modo que pueda tener una visión global y después refinar su diseño.

Uno de los mayores beneficios de las tarjetas CRC es la comunicación. Se hace mejor en tiempo real, en grupo, sin computadores. Cada persona es responsable de varias clases (que al principio no tienen nombres ni otra información). Haga una simulación en vivo resolviendo un escenario cada vez, decidiendo qué mensajes se envían a los distintos objetos para satisfacer las necesidades de cada escenario. Al pasar por este proceso, descubrirá las clases que necesita con sus responsabilidades y colaboraciones, y rellenará las tarjetas a medida que lo hace. Cuando haya pasado por todos los casos de uso, debería disponer de una primera versión bastante completa de su diseño.

Antes de empezar a usar tarjetas CRC, mis experiencias de consultoría más exitosas a la hora de elaborar un diseño inicial consistían en ponerme delante de un equipo que nunca había construido un proyecto POO y dibujar objetos en una pizarra blanca. Hablábamos sobre cómo los objetos deberían comunicarse unos con otros, y borrábamos algunos de ellos para reemplazarlos por otros objetos. En efecto, yo gestionaba todas las «tarjetas CRC» en la pizarra. Realmente, el equipo (que conocía lo que el proyecto se suponía tenía que hacer) creó el diseño; ellos «poseían» el diseño en lugar de recibirlo hecho. Todo lo que yo hacía era guiar el proceso haciendo las preguntas correctas, poniendo a prueba las suposiciones, y recogiendo la retroalimentación del equipo para modificar esas suposiciones. La verdadera belleza del proceso era que el equipo aprendía cómo hacer diseños orientados a objetos no revisando ejemplos abstractos, sino trabajando sobre el diseño que más le interesaba en ese momento: el suyo.

Una vez que tenga una serie de tarjetas CRC, quizá quiera crear una descripción más formal de su diseño usando UML [24]. No necesita usar UML, pero puede servirle de ayuda, especialmente si quiere poner un diagrama en la pared para que todo el mundo reflexione sobre él, lo cual es una buena idea. Una alternativa a UML es una descripción textual de los objetos y sus interfaces, o, dependiendo de su lenguaje de programación, el propio código [25].

UML también proporciona una notación de diagramas adicional para describir el modelo dinámico de su sistema. Eso es útil en situaciones en las que las transiciones de estado de un sistema o subsistema tienen tanto peso que necesitan sus propios diagramas (como en un sistema de control). También puede necesitar describir las estructuras de datos, para sistemas o subsistemas en los que los propios datos son un factor dominante (como una base de datos).

Sabrá que ha terminado la fase 2 cuando haya descrito los objetos y sus interfaces. Bueno, la mayoría: suele haber algunos que se escapan y no se dan a conocer hasta la fase 3. Pero no pasa nada. Lo único que le importa es descubrir tarde o temprano todos sus objetos. Es bueno descubrirlos pronto pero la POO proporciona suficiente estructura de modo que no es grave si los descubre más tarde. De hecho, el diseño de un objeto suele ocurrir en cinco etapas, durante todo el proceso de desarrollo del programa.

Las cinco etapas del diseño de objetos

La vida del diseño de un objeto no se limita a la escritura del programa. En cambio, el diseño de un objeto ocurre en una secuencia de etapas. Es útil tener esta perspectiva porque deja de esperar alcanzar la perfección enseguida; en lugar de eso, se dará cuenta de que la comprensión de lo que hace un objeto y del aspecto que debería tener se produce con el tiempo. Esta visión también se aplica al diseño de varios tipos de programas; el patrón para un tipo particular de programas surge a fuerza de pelearse una y otra vez con ese problema (los Patrones de Diseño se desarrollan en el Volumen 2). Los objetos, también, tienen sus patrones que surgen del entendimiento, uso y reutilización.

  1. Descubrimiento de objetos. Esta etapa ocurre durante el análisis inicial de un programa. Los objetos pueden descubrirse viendo los factores externos y los límites, duplicación de elementos en el sistema, y las unidades conceptuales más pequeñas. Algunos objetos son obvios si se dispone de un conjunto de librerías de clases. Las partes comunes entre clases que sugieren clases base y herencia pueden aparecer enseguida, o más tarde en el proceso de diseño.

  2. Montaje de objetos. Mientras construye un objeto descubrirá la necesidad de nuevos miembros que no aparecieron durante la fase de descubrimiento. Las necesidades internas del objeto pueden requerir otras clases que le den soporte.

  3. Construcción del sistema. Una vez más, pueden aparecer más requisitos para un objeto en esta etapa posterior. Conforme aprende, hace evolucionar sus objetos. La necesidad de comunicación e interconexión con otros objetos en el sistema puede cambiar las necesidades de sus clases o requerir clases nuevas. Por ejemplo, puede descubrir la necesidad de clases facilitadoras o auxiliares (helper), como una lista enlazada, que contienen poca o ninguna información de estado y que simplemente ayudan a funcionar a otras clases.

  4. Extensión del sistema. Cuando añada nuevas características a un sistema puede descubrir que su diseño previo no permite extender el sistema fácilmente. Con esta nueva información, puede reestructurar partes del sistema, posiblemente añadiendo nuevas clases o jerarquías de clases.

  5. Reutilización de objetos. Ésta es la verdadera prueba de estrés para una clase. Si alguien intenta reutilizarla en una situación completamente nueva, probablemente descubrirá algunos defectos. A medida que modifique una clase para adaptarla a más programas nuevos, los principios generales de la clase se verán más claros, hasta que consiga un tipo verdaderamente reutilizable. Sin embargo, no espere que la mayoría de los objetos del diseño de un sistema sean reutilizables -es perfectamente aceptable que la mayor parte de los objetos sean específicos para el sistema. Los tipos reutilizables tienden a ser menos comunes, y deben resolver problemas más generales para ser reutilizables.

Directrices para desarrollo de objetos

Estas etapas sugieren algunas directrices cuando se piensa sobre el desarrollo de clases:

  1. Permita que un problema específico dé lugar a una clase, después deje que la clase crezca y madure durante la solución de otros problemas.

  2. Recuerde, descubrir las clases que necesita (y sus interfaces) supone la mayor parte del diseño del sistema. Si ya tuviera esas clases, sería un proyecto fácil.

  3. No se esfuerce por saber todo desde el principio; aprenda conforme avanza. Ocurrirá así de todos modos.

  4. Comience a programar; consiga tener algo funcionando para poder confirmar o refutar su diseño. No tenga miedo a que acabe haciendo código procedural espagueti -las clases dividen el problema y ayudan a controlar la anarquía y la entropía. Las clases malas no estropean las buenas.

  5. Manténgalo siempre simple. Pequeños objetos claros con utilidades obvias son mejores que grandes interfaces complicadas. Cuando aparezcan los puntos de decisión, aplique el principio de la Navaja de Occam: Considere las alternativas y elija la más simple, porque las clases simples casi siempre son mejores. Empiece con clases pequeñas y sencillas, y podrá ampliar la interfaz cuando la entienda mejor, pero con el paso del tiempo es difícil eliminar elementos de una clase.



[24] Para empezar, recomiendo el mencionado UML Distilled.

[25] Python (www.python.org) suele utilizarse como «pseudocódigo ejecutable».