jueves, 6 de febrero de 2014

Clases intercambiables utilizando polimorfismo. (1ª Parte)

En este artículo aprenderemos a:


§ Usar clases derivadas polimórficamente.
§ Crear una clase que deriva de una clase UserControl.

A continuación utilizaremos el polimorfismo, para resolver una tarea de programación. El polimorfismo hace referencia a una instancia de una clase derivada a través de una variable de referencia a la clase base. (Ver Clases base y abstractas) Cuando llamemos a un método o utilicemos una propiedad, serán definidos en la clase derivada. De este modo, las clases derivadas podrán responder de diferentes maneras a la llamada al mismo método. El polimorfismo simplifica la programación y hace que el diseño más fácilmente extensible.



Un creador de imágenes de piezas de ajedrez


Crearemos una aplicación que permite a un usuario  crear las piezas del tablero de ajedrez mediante la importación de un archivo de mapa de bits creado en otra aplicación, como Paint. O dibujándolo directamente desde la aplicación.
El usuario selecciona una pieza haciendo click en una de las piezas en el panel. Cuando hace click en él, la pieza se muestra en un editor que permite al usuario modificarla y guardar el modelo de pieza modificado se guarda en el panel de piezas.

Diseño del creador de imágenes de las piezas


En este caso en vez de referirnos a las piezas de ajedrez conceptualmente, las crearemos “físicamente” para su representación en pantalla, el programa permitirá elegir entre bitmaps o dibujos conceptuales diseñados a partir de líneas.
Ejecutaremos mediante polimorfismo dos tareas de programación. Se tratará de mostrar el editor correcto basándose en el patrón seleccionado (dibujo de Paint o del editor de líneas de la aplicación). Posteriormente crearemos nuevas instancias de piezas dibujadas.

Cómo diseñar las clases del editor de piezas


Para los dos tipos diseñadores usaremos la misma clase base para hacer un creador de piezas que manejará los dos diferentes diseñadores: un tipo dibujado y un tipo bitmap.
Para ello escribiremos código para trabajar con ambos tipos. Posteriormente podríamos añadir más modos de creación de piezas sin la necesidad de reescribir dicho código.

Este modo de trabajar tiene como ventajas que el código es menos repetitivo pues prescindiendo del polimorfismo nos veríamos obligados a crear un bloque de código que crease una nueva pieza y mostrase un editor para ella. Luego sería necesario crear otro bloque de código idéntico para hacer lo mismo con el editor de bitmap. Con el polimorfismo podemos escribir, depurar y mostrar una pieza, una sola vez. Y las diferencias de código  pueden ir en la clase derivada.

Además se puede añadir fácilmente un nuevo tipo de pieza, pues las instancias de las clases derivadas se suministran en tiempo de ejecución con lo que es posible extender la aplicación para implementar clases derivadas adicionales.

Finalmente la aplicación necesitará menos clases, pues crearemos una clase CPieza para la aplicación que tendrá las clases derivadas CLineaPieza y CCBitmapPieza. Limitaremos las referencias a CLineaPieza  y CCBitmapPieza a un método del código de cliente. El resto del código de cliente utilizará únicamente las referencias a instancias de piezas. De este modo podemos reducir el número de clases, simplificando así la tarea de programación.

A continuación vamos a diseñar una clase base de clase-pieza-que contiene la funcionalidad para las dos operativas, una pieza dibujada y una pieza de mapa de bits. Posteriormente se puede ampliar con más tipos de piezas, sin tener que reescribir la clase base existente.

La clase CPieza que implementaremos:

§ Será capaz de hacer copias de sí misma con el método Clone

Si se hace click en una pieza particular en el panel de piezas, la instancia de la pieza hará una copia de sí misma. El código de cliente no necesita conocer el tipo de la clase derivada. Solamente tiene que pedir una copia, y a continuación, utilizar la copia para su edición.  

§ Añadir un método al editor a través de ObtenerEditor para que devuelva un control de usuario personalizado.

Como el editor se deriva de UserControl si queremos modificar una pieza, el código de cliente necesita pedir a una instancia de pieza una instancia del editor, Para mostrarlo en tiempo de ejecución es necesario añadirlo a la Colección Controls del formulario. El editor de esta aplicación también se representa por una clase base, la clase CEditorPieza.

 La clase CEditorPieza a implementar será:

§ Implementar un evento guardado.

La interfaz de usuario responde al evento salvado moviendo la pieza editada en el   panel de piezas y elimina el editor del formulario. La eliminación del editor es tan sencilla como eliminar de este un UserControl de la colección de controles del formulario.

§ Derivar de la clase UserControl.
           
Añadiendo una instancia a la colección de controles del formulario y mostrando el formulario en tiempo de ejecución, seremos capaces de desplegar el editor como una unidad.


El siguiente gráfico muestra el UML para la clase base y el editor de piezas

UML de clases base abstractas

Hay que implementar por derivación cada tipo de pieza que pertenezca a ambos editores. Una clase CPieza y un Editor de Piezas. La relación entre las clases base y las clases que dibujan las piezas se muestra aquí en UML:

UML editor para dibujar piezas ajedrez

Es importante entender que CPieza y CEditorPieza nunca serán instanciados. Sólo las clases derivadas CLineaPiezas y EditorDibujos, CBitmapPieza (no se muestra  en el diagrama anterior y crean instancias de Bitmap_Editor_Pieza). Asimismo, recuerda que  CEditorPieza sólo crea instancias Dibujar_CEditorPieza CBitmapPieza crea sólo instancias de Bitmap_CEditorPieza.

Usando estas clases, el control básico de flujo en el formulario aparece como esto:


1. En el inicio, la aplicación carga unas piezas en el panel de piezas. La clase CPieza  implementa un método de dibujo para facilitar esto. Este código de inicio no utiliza polimorfismo, porque las clases derivadas deben ser instanciadas específicamente.

2. El usuario hace click en una de las plantillas, que es una instancia de cualquiera de la clase CLineaPieza o la clase CBitmapPieza. El controlador de eventos para el evento click no determina el tipo de método de dibujo derivado al hacer click, sino que simplemente accede a la instancia a través de una referencia de CPieza.

3. Se crea una copia de la instancia llamando al método Clone. Esta llamada se comporta de forma polimórfica.

4. Mediante una llamada al método ObtenerEditor se crea  una instancia de CEditorPieza de la instancia seleccionada. Esta llamada también se comporta
Polimórficamente.

5. Se deriva la instancia CEditorPieza  de UserControl  y se agrega a la colección de controles del formulario. Luego se muestra en el formulario.

6. El usuario modifica la pieza mediante el CEditorPieza.

7. Si el usuario hace clic en el botón Guardar, que forma parte del control CEditorPieza. El controlador de eventos Click para el botón Guardar guarda los
cambios en la pieza y dispara el evento de guardado del formulario.

8. En respuesta al evento guardar, la instancia del dibujo de la pieza se añade al panel de dibujos de piezas de la clase CEditorPieza -un control de usuario- y la colección de controles del formulario  la recoge y la almacena.


Las clases Base


 CPiezaCEditorPieza son las dos clases base del proyecto por eso tienen muy pocas funciones miembro. Tienen las funciones necesarias para crear, dibujar, editar y salvar las instancias de los dibujos de las piezas. Estas funciones crean la interfaz que utilizará todo el código. El resto del comportamiento será implementado en las clases derivadas.

 

Crear la clase CPieza


La clase CPieza sólo tiene tres miembros y es una clase abstracta, es decir, que no puede ser instanciada, por tanto hay que derivar otra clase de ella. Esto deja a la totalidad de la implementación a las clases derivadas, lo cual es apropiado teniendo en cuenta lo variadas que la pueden ser las clases derivadas.

Crearemos un nuevo proyecto de aplicación Windows de nombre DibujadorDePiezas. Agregaremos una nueva clase al proyecto de nombre CPieza y modificaremos la declaración de la clase para incluir la palabra clave abstract, se muestra en negrita:

Public MustInherit Class CPieza

End Class

Añadir los siguientes miembros abstractos a la clase:

Public MustOverride Sub Dibujar ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs)
Public MustOverride Function ObtenerEditor() As CEditorPieza
Public MustOverride Function Clone() As CPieza

Hay que tener en cuenta que las propiedades y métodos de la clase CPieza se refieren sólo a las clases CPieza y CEditorPieza. En las clases derivadas, el método devuelve una instancia ObtenerEditor de cualquiera de las clases Dibujar_Pieza y BitMap_Pieza. El tipo que  retorna ObtenerEditor es CEditorPieza, que permite a las clases derivadas devolver cualquier tipo que  derive de CEditorPieza.

El método Dibujar es muy similar al método Paint de los controles de formulario de  Windows. Se puede añadir este método como un controlador de eventos con el método Paint de cualquier control. Esto será bueno para crear la parte de interfaz de usuario del proyecto.

También es posible añadir la nueva instancia a la colección Controls del formulario, porque la clase CEditorPieza deriva de UserControl. El método clone  devuelve una copia de la instancia CPieza. En las clases derivadas, la instancia devuelta puede ser de cualquiera de las clases o Dibujar_Pieza o BitMap_Pieza

Crear la clase CEditorPieza


CEditorPieza es una clase derivada de la clase UserControl que implementa un  evento guardado. En este caso, la clase no se declara como clase abstracta porque se quieren diseñar  las clases derivadas en el Diseñador de Windows Forms y para ello, la clase debe heredar de una clase concreta (no abstracta).

Añadir un UserControl al proyecto y llamarlo CEditorPieza.

Añadir la declaración para el evento Guardar de la clase CEditorPieza

Public Class CEditorPieza
    Public Event Salvar(ByVal sender As Object, ByVal e As EventArgs)
End Class

Añadir el siguiente método al disparar el evento Salvar de la clase CEditorPieza

Los eventos en las clases base no pueden ser disparados en las clases derivadas, este método debe ser accesible para las clases derivadas, para disparar el evento Salvar no se puede implementar un evento Salvar en cada clase derivada, para que el evento se comporte polimórficamente, debe ser declarado en la clase base.

Public Sub DisparoSalvar(ByVal sender As Object, ByVal e As EventArgs)
        RaiseEvent Salvar(sender, e)
End Sub

Las Clases Derivadas


Para utilizar las clases de forma polimórfica no se pueden añadir miembros públicos de la clase, pero debido a que se tienen que crear instancias de las clases derivadas, cada clase derivada de CEditorPieza necesita un constructor personalizado que acepte una instancia de la clase derivada de CPieza y un miembro para almacenarla.

Para cada tipo de dibujo, se implementará un par de clases que se derivarán de CPieza y CEditorPieza. Las clases que derivarán de CEditorPieza implementarán los eventos de salvado. En las clases derivadas de CPieza se implementarán los miembros abstractos y se añadirán los miembros para la creación de nuevas instancias.
Para acceder a los miembros de las clases derivadas será necesario usar una referencia a la clase derivada

 

Crear la clase CLineaPieza


La imagen de abajo es un dibujo creado por el usuario de 60x60 píxel. El usuario creará la pieza dentro de este espacio.

Dibujo editor pieza ajedrez

Para construir la clase que maneja esta funcionalidad hay que agregar  una nueva clase al proyecto de nombre  CLineaPieza. Después agregaremos una instrucción Imports  al principio del fichero fuente de  CLineaPieza para incluir el espacio de nombres System.Drawing. Los puntos se almacenarán como una matriz de tipo System.Drawing.Point. Agregar la instrucción imports  permite usar el nombre no calificado Point, en el código.

Imports System.Drawing

Modificar la declaración de la clase para indicar que la clase deriva de la clase CPieza.

Public Class CLineaPieza
    Inherits CPieza
End Class

Añadir el siguiente array y la propiedad para almacenar los puntos.

Private m_puntos() As Point = New Point() {}

Public Property Puntos() As Point()
    Get
      Return (m_puntos)
    End Get
    Set(ByVal Value As Point())
      m_puntos = Value
    End Set
End Property

Para definir el método Dibujar el código de cliente se puede asignar como un manejador de eventos a cualquier control que dispare el evento Dibujar.

Public Overrides Sub Dibujar(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs)
        e.Graphics.DrawRectangle(Pens.Black, 0, 0, 60, 60)
        Dim point As Integer
        For point = 0 To m_puntos.Length - 2
            Dim puntoInicio As Point = m_puntos(point)
            Dim puntoFin As Point = m_puntos(point + 1)
            e.Graphics.DrawLine(System.Drawing.Pens.Black, puntoInicio, puntoFin)
        Next
End Sub

Al definir el método ObtenerEditor se producirá un error de compilación en este punto porque no hemos implementado aún la clase EditorDibujos.

Public Overrides Function ObtenerEditor() As CEditorPieza
Return (New EditorDibujos(Me))
End Function

Vamos a definir el método Clone. Este método reserva nueva memoria  para todos los objetos contenidos en la nueva instancia y copia el valor de la instancia me en una nueva instancia.

Public Overrides Function Clone() As CPieza
Dim nuevaPieza As New Dibujar_Pieza
nuevaPieza.m_puntos = CType(m_puntos.Clone(), Point())
Return (nuevaPieza)

End Function



No hay comentarios:

Publicar un comentario