En este artículo, vamos a crear
una clase abstracta, una desde la que se debe heredar. En la definición
de la clase abstracta, se le determinan
los miembros que la clase derivada debe implementar.
.NET proporciona varias clases
abstractas, diseñadas exclusivamente como clases base para que los desarrolladores
las utilicen para
crear nuevas colecciones de clases, también aprenderemos cómo crear miembros de una clase base y de una clase
derivada que tiene el mismo nombre, sin embargo, no se comportan polimórficamente.
Los desarrolladores pueden usar como clases base algunas
proporcionadas por el propio entorno .NET diseñadas exclusivamente con este fin
para poder crear nuevas clases o colecciones de clases. También es posible que
el desarrollador implemente su propia clase base, para ello es necesario
determinar los miembros a implementar por las clases derivadas en la clase
base.
Clases Abstractas
La abstracción es la
característica que nos permite identificar un objeto a través de sus aspectos
conceptuales. Las propiedades de los objetos, pueden hacerlos tan distintos que
sea difícil reconocer que pertenecen a la misma clase. Por ejemplo dos objetos
barco, un crucero y una piragua, su aspecto exterior y tamaño son muy
diferentes, sin embargo sabemos que ambos pertenecen a la clase barco.
En el artículo anterior creamos una clase base CPieza y luego derivamos
las clases CTorre, CCaballo, CAlfil etc. Como vemos las clases
representan el mundo real. Aunque el ajedrez es algo real, es mal ejemplo para
explicar las clases abstractas, por que las piezas aunque reales tienen
atributos abstractos, así que vamos a pensar
en algo de la vida real.
Por ejemplo una
empresa (recordando que las clases deben modelar el
mundo real), en dicha empresa hay empleados con diferentes funciones. Por tanto
tendremos una clase CEmpleado. Como se ha dicho la empresa
tiene distintas funciones para cada empleado, por ejemplo el jefe de fábrica,
el jefe de sección, el operario de fresadora, el operario de empaquetadora,
etc. La clase CEmpleado deberá definir el comportamiento común de un empleado genérico, a
pesar de que nunca existirá un empleado genérico. Utilizaremos la herencia para
crear clases derivadas de la clase genérica (abstracta) de empleado.
Otra
razón para no crear una clase base, es que se puede crear una instancia y tener
la tentación de agregar funcionalidad a la clase base que no es apropiada para
derivar clases. Este error es más probable si el diseño se comenzó como una
clase normal y corriente CEmpleado y luego derivamos una clase de ella. Supongamos que la empresa
comenzó sólo con un tipo de empleado y se creó la clase CEmpleado, cuando
la empresa creció, creó nuevos tipos de
empleado y agregó unas clases COperarioM1, COperarioM2 y CJefeSeccion que se decidió derivar de CEmpleado. Debido
a que la nómina de los operarios de máquina iba a tener un incentivo por buen
uso de la máquina, al igual que la clase CEmpleado, lo único que se
necesitaba era agregar el método Incentivo_Uso a CEmpleado, pero
por herencia ahora se debe ofrecer a todos los empleados ya sean operarios o jefes el incentivo de
buen uso de la máquina. La solución a este problema es crear una clase base
(abstracta) para COperarioM1, COperarioM2 y CJefeSeccion por ejemplo,
CEmpleado_Generico y
luego agregar el método Incentivo_Uso1 e IncentivoUso2 respectivamente a las clases COperarioM1 y COperarioM2, de este modo se podrá
definir el método para calcular el incentivo, a cada operario en función de su
máquina y otros empleados (jefes) no dispondrán de dicho incentivo.
Para
evitar la implementación de métodos en las clases base, Visual Basic ofrece la
opción de clase base abstracta que permite crear una clase base de la que
deriven otras clases pero aunque es posible definir la clase base entera,
obliga a implementar los métodos específicos en las clases derivadas. Se pueden
declarar referencias a la clase abstracta, pero no se pueden crear instancias
de ellas. Como resultado, no se perderá nada del comportamiento polimórfico de
las clases derivadas.
Encapsulación
Establece la
separación entre el interfaz del objeto y su implementación. Una clase bien
encapsulada no debe permitir la modificación directa de una variable de la clase ni ejecutar métodos
que sean internos de la clase.
Un programador podrá
utilizar la clase sin necesitar conocer su implementación.
Public.
Será accesible desde todo el código de la clase y de sus clases
derivadas, también desde el código de cliente.
Public Function Metodo1
Private. Sólo será accesible desde la propia
clase
Private Function Metodo1
Protected. Sólo será accesible
desde la propia clase y desde sus clases derivadas
Protected Function Metodo1
Friend.
Será accesible por todo el código de su ensamblado
Friend Function Metodo1
Protected Friend.
Será accesible desde el código de su clase y clases derivadas y por todo el
código de su ensamblado.
Protected
Friend Function Metodo1
Rediseño de la clase CPieza
La clase CEmpleado_Generico puede
ser completamente funcional, y puede llevar a errores en el
programa si alguna vez es instanciada la clase porque su comportamiento no sería el de una clase COperarioM1 ni
una clase CEmpleado. Se tiene que crear la clase CEmpleado_Generico como una clase abstracta. La clase CEmpleado_Generico
tendría una propiedad ID cuyo formato depende del tipo de empleado. Debido a que la propiedad
ID depende del tipo de empleado, no hay razón para implementar este método en la clase base y además,
no debería implementarse en la clase base. Si se hizo así, la implementación no sería correcta para cualquier tipo de empleado. Usando una propiedad
abstracta en la clase base obligamos a implementar este método en las clases derivadas. Volviendo al
ejemplo del ajedrez en
este documento, también se agregará el método abstracto Mover Si este método se implementa en la clase base, no sería válido para ninguna
de las clases derivadas.
Se nos puede ocurrir implementar el método Mover en la clase base, luego verificamos el tipo de la instancia y posteriormente movemos la pieza dependiendo del tipo que sea. "Esta solución funciona, siempre y cuando sepamos cuales son todas las clases derivadas de la clase base, pero hacer esto limitaría la capacidad de reutilización de la clase base. En general, las clases base no deben contener código que depende de las clases derivadas.
Descripción del diseño usando Lenguaje de Modelado Unificado (UML)
El
Lenguaje de Modelado Unificado (UML) es una herramienta gráfica para describir el
diseño orientado a objetos. Estas herramientas de desarrollo permiten discutir
los diseños con un vocabulario común. También se disminuye la ambigüedad en la
especificación. La unidad básica del diagrama de clases es la caja, que
representa a la clase:
El
elemento de clase consta de dos secciones a continuación del nombre, uno para
especificar las propiedades y otro para especificar los métodos:
El
diagrama muestra la clase base abstracta CPieza. El título en cursiva indica que
CPieza es una
clase abstracta. En UML se indican los
valores por defecto de las listas de propiedades, y especifica las propiedades
públicas colocando un signo más delante de ellas.
A
continuación, en la parte inferior del cuadro se enumeran los métodos públicos definidos
en la clase. UML también indica los parámetros de los métodos y sus valores de
retorno. La palabra “in” indica que el
parámetro se pasa por valor. Aquí se muestra el diagrama completo de clases UML.
UML
también muestra las clases derivadas de la relación de herencia mediante una flecha
que apunta hacia la clase base. Las clases derivadas heredarán todo de la clase
base, por lo que no se muestran los métodos, sólo se muestran si hay adiciones en las clases derivadas.
El diagrama de clases UML no
especifica el comportamiento de la interfaz. Por ejemplo, No se explica cómo
deben implementarse las propiedades ni los métodos.
Creando
la Clase
abstracta
La primera clase que creamos era CPieza, vamos a declararla de nuevo como clase
abstracta, para ello basta con hacer.
Public
MustInherit Class CPieza
La declaración de las variables y
métodos no abstractos, se queda como estaban en el anterior documento, pero las
funciones abstractas ya no pueden llevar implementación por lo que quedan del
siguiente modo.
Añadir
miembros abstractos
'Método padre vacio, se definirá en cada clase hija
Public MustOverride Function Mover(ByVal
NuevaPos As String)
As Boolean
La declaración en la clase base no se puede implementar porque se debe hacer en la clase
derivada. Al añadir la palabra clave MustOverride, se
obliga a que la propiedad o método se
definan en cada clase
derivada.
Si se declara uno de los
miembros de una clase utilizando esta
palabra clave, También se debe declarar la clase como abstracta. Los elementos declarados
permanecen como lista de tareas hasta que se implemente. Sin
embargo, si se declara una clase como abstracta, no obliga a dejar vacías las
declaraciones de cualquiera de los miembros de la clase
abstracta.
Si hay múltiples niveles de herencia
hay que aplicar algunas normas:
Supongamos por ejemplo, que se
utilice CPieza como una clase base para las clases CTorre y CCaballo. Si se implementa Mover en CPieza, no se está obligado a implementarlo en CTorre y CCaballo. CTorre y CCaballo pueden heredar la
implementación que haya en CPieza.
Si el método en la clase padre se
declara MustOverride, se obligará a declararlo en las clases hijas. En este caso la declaración del método no se
pone.
Una Clase Colección
Existe en la biblioteca de clases de .NET una clase ya creada llamada ArrayList es muy flexible porque se puede añadir cualquier tipo de objeto a la misma. La desventaja de usar la clase ArrayList es que se puede agregar accidentalmente un objeto que no sea del tipo de la clase, es
posible que se produzca un error cuando se
recupere el objeto de la clase
ArrayList.
Una manera razonable de evitar errores
en la conversión de tipos es crear
una clase que acepte un único tipo de instancia
de tipo CPieza y devuelva sólo los casos de instancias de CPieza.
.NET proporciona la clase
abstracta collection que se puede utilizar como clase base para las clases colección escritas. La
clase collection sólo permite agregar o quitar un tipo de objeto. Esto significa los errores
aparecerán en tiempo de compilación en lugar de en
tiempo de ejecución.
Derivar de una clase abstracta de .NET para
crear una clase Colección
En la documentación de las clases System.Collections.DictionaryBase dice:
"Proporciona la clase base abstracta (MustInherit) de la clase base para una
colección con establecimiento inflexible de las claves y valores asociados”.
Añadir
el método Añadir
Vamos a crear
un array de piezas en la clase CPieza, para ello utilizaremos
la clase base predefinida para las colecciones. En la declaración de la clase
ponemos:
Public Class CPieza
Inherits
System.Collections.DictionaryBase 'Se usará para crear un array de piezas.
Después añadimos el procedimiento Añadir que
añade una instancia de CPieza a la colección.
Public Sub Añadir(ByVal
unaPieza As CPieza)
Me.Dictionary.Add(unaPieza.Nombre,
unaPieza)
End Sub
Ahora nadie va a ser capaz de añadir nada a
la colección que no sea una instancia de CPieza Además, la
pieza siempre será mostrada con su nombre.
Añadir el método Eliminar
Public Sub Eliminar(ByVal
title As CPieza)
Me.Dictionary.Remove(title)
End Sub
Añadir la propiedad ExistePieza
Esta propiedad lleva delante la palabra clave
Default que significa que cuando llamamos al entrar en la
clase CPieza lo primero que
se ejecuta es esta propiedad que verifica si la pieza a meter ya existe en la
colección.
Default Public ReadOnly Property ExistePieza
(ByVal title As
String) As
CPieza
Get
If Me.Dictionary.Contains(title) Then
Return CType(Me.Dictionary(title), CPieza)
Else
Return Nothing
End If
End Get
End Property
Si queremos crear un par de funciones en el código cliente para
añadir y eliminar objetos de la colección, en el código del Formulario podemos
hacer
Private m_Piezas As New CPieza
Public Sub MeterPieza(ByVal
nuevaPieza As CPieza)
m_Piezas.Añadir(nuevaPieza)
End Sub
Public Function SacarPieza(ByVal
title As CPieza) As
CPieza
Dim Pieza As CPieza = m_Piezas
m_Piezas.Eliminar(title)
Return (Pieza)
End Function
Y entonces en el Form_load podemos hacer lo siguiente
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles
Me.Load
Dim T As
New CTorre
Dim EnColeccion As CPieza
T.Nombre = "TD"
T.Añadir(T)
'añadir una torre directamente a la colección
MeterPieza(T) 'añadir
una torre a través de las funciones cliente
EnColeccion = T.ExistePieza("TD") 'recupera
la torre de la colección
T.Eliminar(T)
'Elimina la torre directamente de la colección
SacarPieza(T)
'Elimina la torre a través de las funciones
cliente
End Sub
Variaciones en la herencia
Hay otras dos variaciones de
la herencia que merecen una mención. Las clases
selladas
que permiten impedir la herencia. También pueden impedir que los miembros de la base se comporten polimórficamente.
que permiten impedir la herencia. También pueden impedir que los miembros de la base se comporten polimórficamente.
Clases Selladas
Algunas veces no se debe permitir a los
programadores usar una clase como clase base, se puede prevenir esto usando la
palabra clave NotInheritable:
NotInheritable Class NotABaseClass
End Class
Ocultar miembros de clase base
La
palabra clave Shadows indica que
aunque un método en la clase derivada tenga el mismo nombre que un método virtual en la clase base, el método de la clase derivada no está destinado a ser el reemplazo del de la clase base del método
virtual de la clase. El efecto es que una referencia a la clase base llama al método
base y una referencia a la clase derivada llama al método
derivado. La palabra clave Shadows se aplica a todos los métodos del mismo nombre en la clase base.
Class ClaseBase
Public Sub MetodoBase()
Console.WriteLine("MetodoBase
en Clase base.")
End Sub
End Class
Class ClaseDerivada
Inherits ClaseBase
Public
Shadows Sub MetodoBase()
Console.WriteLine("MetodoBase in
clase derivada.")
End Sub
Public
Shared Sub Main ()
Dim
derivado As New ClaseDerivada()
derivado.MetodoBase()
Dim
base As ClaseBase = derivado
base.MetodoBase()
End Sub
End Class
La salida
de Main es
MetodoBase en la
clase derivada.
MetodoBase en la
clase base.
Se debe usar Shadows con precaución, pues los programadores esperan
derivar clases preferentemente para actuar polimórficamente.
No hay comentarios:
Publicar un comentario