1 de diciembre de 2014

Diseñar y crear clases

Artículo original: Designing and Building Classes
http://weblogs.foxite.com/andykramek/2005/03/27/designing-and-building-classes/
Autor: Andy Kramek
Traducido por: Ana María Bisbé York


He aquí otro tema sobre el que he sido preguntado con frecuencia y es precisamente cómo se deben diseñar las clases. Por supuesto, no hay una única respuesta "correcta"; pero he encontrado siempre muy útil al crear una clase nueva, preguntarme estas tres preguntas:

  • ¿Será reutilizado el objeto?
  • ¿La clase va a simplificar la tarea o a complicarla?
  • ¿Vale la pena el esfuerzo?

Ahora, para ganar en claridad, permítanme que me expanda un poco en esto:

Re-usabilidad

Este es, probablemente la razón más común para la creación de una clase y la posibilidad de re-utilizarla, es después de todo, uno de los logros primarios de POO. En su nivel más simple, puede significar tan sencillo como establecer sus preferencias a los objetos - propiedades Font, Color y Style, por ejemplo, de tal forma que todos los objetos de sus clases se creen con la configuración correcta. Las cosas se complican un poco cuando consideramos la funcionalidad. En la práctica, ¿con qué frecuencia hacemos exactamente lo mismo, en exactamente la misma forma, para alcanzar exactamente el mismo resultado? La respuesta es probablemente "no muy frecuentemente" y empezará a asombrarse si la re-usabilidad, es después de todo, realmente tan valiosa. Pasemos al segundo criterio.

Controlar complejidad

Realmente es muy raro que las clases funcionales puedan simplemente ser re-utilizadas "tal cual". Existen casi siempre, diferencias en el entorno, la entrada o salida de datos requeridos y a veces, todos juntos. Esta complejidad aparente puede a veces oscurecer el tema, con la funcionalidad que se desea mantener constante, incluso a través de la implementación, puede diferir en detalle entre las aplicaciones.

Al aplicar las reglas para el diseño de clases resumidas en el siguiente tópico, debe ser más fácil decidir si al utilizar una clase va a facilitar el control de esta complejidad o no. Incluso, si el criterio de una clase pueda facilitar la manipulación de la complejidad, nosotros aun tendremos que considerar el tercer criterio.

Vale la pena el esfuerzo

Un objeto debería ser encapsulado para que contenga en si mismo toda la información necesaria para completar su tarea, e incluso evidente que ningún objeto debe confiar su implementación interna a otro objeto. Claramente esto puede complicarnos mucho la vida. Puede significar que su clase tendrá que verificar docenas de condiciones posibles para determinar (para si) exactamente que el estado del sistema es antes de que pudiera realizar la función asignada. Al considerar la creación de una clase nueva, es importante estar seguros de que realmente vale la pena el esfuerzo de hacer tal cosa.

¿Qué tipo de clase queremos?

Al tener que decidir que realmente deseo crear una clase nueva, la siguiente pregunta a responder es: ¿Qué tipo de clase debo diseñar?" Existen dos modelos fundamentales para el diseño de clases que son el "Mundo Real" o un modelo concreto de "Componente" o modelo abstracto. No hereda uno del otro, son precisamente diferentes.

Modelo Mundo real (concreto)

En este modelo las clases se diseñan para cumplir la funcionalidad requerida en su totalidad. En este modelo se basan usualmente los Objetos de negocio. Por ejemplo, podemos definir a una clase objeto de negocios "persona" que tiene las características comunes (por ejemplo, propiedades para Ojo izquierdo, ojo derecho, nariz, etc) y el comportamiento de las personas (ejemplo Métodos de Respiración, Alimentación, etc). Esta clase persona puede ser sub-claseada para modelar la funcionalidad de tipos especiales de personas tales como "clientes" o "amigos". Es realmente aparente que el modelo real confía demasiado en la herencia y , como un resultado, en tiempo de diseño, está explícitamente definida la funcionalidad  .

Modelo Componente (abstracto)

En este modelo la clase se ocupa más de un comportamiento específico, que de funcionalidad. Cada clase define una función genérica o acción y, de manera aislada, limita la usabilidad. Claramente habrá más confianza en la herencia, en su lugar, el modelo va a depender de la agregación (esencialmente una operación en tiempo de ejecución) y la composición (en tiempo de ejecución y diseño). El equivalente para nuestra clase objeto de negocio Persona es un compuesto de objetos separados: dos instancias para las clases ojos y una instancia para la clase nariz, etc.

Identificar responsabilidades

Habiendo decidido qué modelo va a utilizar. la siguiente tarea es estar absolutamente seguro de que sabe lo que la clase va a hacer. Esto puede sonar obvio; pero hay una trampa sutil en esto. Es muy fácil crear demasiado en una clase, antes de que se de cuenta que lo que ha creado que en realidad no es re-utilizable, porque hace demasiado. El mejor proceder para esto es identificar y categorizar las "Responsabilidades" de la clase antes de que comience a escribir código alguno. Una responsabilidad puede estar definida, de forma muy simple, como algún elemento de funcionalidad que tiene que ser completado.

Categorizar responsabilidades (Las reglas "debe, podría haber y debe ser hecho").

El propósito de la categorización es asegurarse de que puedo identificar correctamente donde en la jerarquía de clases, pertenece cada responsabilidad. Yo hago esto al utilizar tres categorías las que llamo "debe hacer", "podría hacer" y "debe ser hecho"

Las responsabilidades que caen dentro de la primera categoría "debe hacer" son aquellas que derivan de un objeto desde la clase que debe hacer en cada situación y que no puede ser hecho por otro objeto. Esto es, claramente, la responsabilidad de la clase. Debe ser parte de la definición de clase raíz. Por ejemplo, cualquier objeto que mueve el puntero del registro en una tabla, cursor o vista, DEBE chequear las condiciones EOF() y BOF().

Las responsabilidades que entran en la categoría "podría hacer" son indicadores de que la clase puede requerir una o más subclases (o, posiblemente, la cooperación entre objetos de otra clase.) En otras palabras, estas son cosas que es posible que haga la clase; pero que no son precisamente requeridas en cada situación. Típicamente es un fallo reconocer aquí estos elementos que "podría hacer" muy pronto en el proceso de diseño que encamine a una inadecuada colocación de la funcionalidad en la jerarquía de clases y la selección sea hecha. Sin embargo, un objeto que muestre una lista PUEDE inhabilitarse a si mismo una vez que sea hecha la selección. Sin embargo, esto no es la función esencial y no es el comportamiento requerido en cada momento. Esto pertenece a una subclase especializada.

La categoría final (debe ser hecho) es muy importante también. Esta es la categoría que define la suposición de que una clase puede ser exitosa para funcionar correctamente. Los elementos listados definitivamente no son de única responsabilidad de la clase en cuestión pero pueden, aun así, ser hechos de alguna forma. Por ejemplo, para que un control enlace con un origen de datos, el origen de dato DEBE estar abierto antes de que sea instanciado el control. Sin embargo, controlar esta operación claramente no es responsabilidad del control como tal - si solamente porque debe estar hecho antes de que se instancie el control.

Teniendo definidas y categorizadas las responsabilidades de la nueva clase, puede entonces definir su interfaz pública decidiendo qué propiedades y métodos va a requerir y cómo se deben revelar a otros objetos con los que interactúan. Sólo cuando todo esto esté hecho, puede comenzar a pensar sobre el código y aquí hay un par de cosas que es necesario recordar.

Escribir código en los métodos, no en los eventos.

Al escribir el código de las clases, yo creo firmemente en evitar el colocar código directamente en los eventos nativos de Visual FoxPro, siempre que puedo hacerlo. En su lugar, creo métodos de usuario y los llamo desde los eventos. Hacerlo sí no es, admitámoslo, un requerimiento; pero encuentro que te facilita la vida por varias razones:

  • Me permite dar nombres más identificativos a los métodos. Esto puede sonar trivial; pero realmente hace que sea más fácil de leer y mantener el código cuando el código produce algún tipo de salida es llamado por: "This.GenerarSalida()" en lugar de "Thisform.Pageframe1.Page1.CmdButton1.Click()"
  • Me permite cambiar la interfaz, si es necesario con la simple re-colocación de una única línea que llama al método en lugar de de tener que cortar y pegar la parte funcional del código.
  • Me permite partir los métodos complejos en métodos múltiples (y potencialmente reutilizables). El evento actúa meramente como indicador para el código asociado.

Los métodos deben hacer una cosa y sólo una

Los métodos que hacen "de todo" son difíciles de reutilizar. Recuerde: una simple línea de código es siempre reutilizable. Si tengo que oprimir la tecla Re Pag más de una vez para ver mi código, lo más probable es que el método sea demasiado largo y trate de hacer demasiadas cosas. La solución es partir ese tipo de métodos monolíticos en métodos pequeños y mejor enfocados, y utilizar un método de control que los pueda llamar de forma individual si es necesario. (Pregúntese a si mismo cuántas veces se ha visto duplicando código en un método o bloque de código que ya existe en parte de otro método.

Asegúrese de poner la funcionalidad lo más alto posible en la jerarquía de clases

Esto es una trampa en la que he caído muchas veces a través de los años y aun ocasionalmente tropiezo. Debe saber que cuando coloca funcionalidad muy alto porque invierte mucho tiempo en sus subclases tratando de lograr toda una gran funcionalidad que pueda en la clase raíz. Uno de los mayores beneficios de esto "Puede, debe, podría" es el que ayuda a evitar esta trampa.

Utilice métodos plantilla para una interfaz inconsistente.

He constatado que necesitamos evitar colocar la funcionalidad en una jerarquía muy alta. Al crear un contenedor, vacío métodos en un nivel más alto de la jerarquía, puedo asegurar que todas las clases van a compartir una interfaz común. Ya que las subclases comparten la interfaz común, son intercambiables. Incluso, si un método vacío es llamado., no hace daño a nadie, porque todos los métodos de VFP devuelven .T. de forma predeterminada, incluso si no contienen texto.

No hay comentarios. :

Publicar un comentario