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
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:
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 y 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
CPieza y CEditorPieza 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.
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