Cuidado con lo que deseas…
…porque se te puede conceder! Esto reza un viejo dicho, y no puede ser más aplicable a la comunidad de desarrolladores Visual Basic. Uno de esos deseos fue tener herencia, sin ningún tipo de limitaciones, cosa que si bien es muy deseable en ciertos contextos también es cierto que mal usada puede traer muchos dolores de cabeza. Pero tampoco vamos a ser alarmistas y pensar que la herencia es algo TAN complejo que solo unos pocos pueden comprender y menos usar en la realidad, nada está mas lejos de la verdad. Obviamente para poder usar de forma efectiva la herencia es muy importante entender en que casos es buena y nos puede aportar algo positivo, y cuando es mejor usar otras técnicas de programación ya conocidas como las interfaces. Y ya que estamos con los dichos, yo resumiría el uso de la herencia para los desarrolladores Visual Basic con uno muy escuchado en estos últimos tiempos: “No tenga miedo, tenga cuidado”.
Visual Basic ha evolucionado mucho desde sus inicios hace más de 10 años, y uno de sus hitos más importantes fue la versión 4.0 con un nuevo paradigma de desarrollo: orientación a objetos (o casi). De hecho, VB4 cumplía con todos los criterios de un lenguaje orientado a objetos salvo uno: herencia, y esto se ha mantenido así hasta el día de hoy.
Visual Basic.NET completa el requisito faltante (y agrega algunos otros más como métodos constructores, sobrecarga de métodos, el modificador Protected y la posibilidad de reemplazar métodos compilados con “Override”, por ejemplo). También incluye la nueva y misteriosa palabra clave Shadows, que no está presente en otros lenguajes.
A primera vista puede ser difícil de entender la diferencia entre las palabras clave Override y Shadows, y darse cuenta de la necesidad de esta última. Incluso podría llegar a cuestionarse la salud mental de los integrantes del equipo de desarrollo de Visual Basic .NET, ¿en que estaban pensando?, ¿eh?.
Bueno, a continuación veremos que (por suerte) esto no es así y exploraremos los pormenores de las palabras clave Override, Overloads y Shadows e intentaremos aclarar la confusión.
Conceptos de Herencia
Si bien estos conceptos no son nada nuevos y son conocidos por muchas personas, vamos a repasarlos un poco mientras creamos nuestro proyecto de prueba para explorar el uso de Overloads, Override y Shadows.
La herencia nos permite crear una nueva clase que automáticamente tenga el comportamiento y métodos de otra existente. De alguna manera es como si estuviéramos fusionando el código de la vieja clase con el de la nueva, pero sin tener que volver a escribirlo (o copy/paste).
Bueno, manos a la obra. Abramos nuestro Visual Studio.NET y creemos un nuevo proyecto de tipo Console Application que se llame “HerenciaVB”.
Para probar los conceptos de herencia vamos a crear una nueva clase base (base class) y una subclase. La clase base tendrá cierta funcionalidad que representa a una entidad de la vida real, por ejemplo un Video. La subclase se derivará de esta clase base por lo que será también un Video, pero una de un tipo mas especializado. De esto se trata la herencia, crear nuevas clases que tienen toda la funcionalidad de una clase base pero son más especializadas o con un foco mas restringido que la original.
Manos a la Obra
Haciendo clic derecho sobre el ítem del proyecto en la ventana Solution Explorer y seleccionando la opción Add à Add Class, agreguemos una nueva clase llamada Video. Esta clase contendrá la funcionalidad básica para representar prácticamente cualquier clase de video, que la podemos implementar con el siguiente código:
Public Class Video
Private strID As String
Private strTitulo As String
Private sngPrecio As Single
Public Property ID() As String
Get
Return strID
End Get
Set(ByVal Value As String)
strID = Value
End Set
End Property
Public Property Titulo() As String
Get
Return strTitulo
End Get
Set(ByVal Value As String)
strTitulo = Value
End Set
End Property
Public Property Precio() As Single
Get
Return sngPrecio
End Get
Set(ByVal Value As Single)
sngPrecio = Value
End Set
End Property
End Class
Con esto tenemos nuestro video base que posee un ID (quizás el número de serie del fabricante), título y precio, cosas que todos los videos tienen en común.
Comienza la Herencia
Ahora, usando nuestra clase de video genérica podemos construir una mas especializada, por ejemplo un DVD. Agreguemos una nueva clase que se llame DVD igual como hicimos con la anterior, usando la opción Add Class del menú del proyecto.
Un DVD incluye la misma funcionalidad que un Video de nuestra clase base, así que lo que vamos a hacer es simplemente heredar de la clase Video escribiendo lo siguiente:
Public Class DVD
Inherits Video
End Class
Con este pequeño cambio nuestra nueva clase DVD ya tiene un ID, Título y Precio, propiedades que están implementadas en la clase base Video. Sin embargo, un DVD puede tener otras propiedades y comportamiento que un Video estándar, como por ejemplo si tiene múltiples pistas de audio. Para ilustrar nuestro punto vamos a implementar este cambio, de los muchos posibles:
Public Class DVD
Inherits Book
Private bMultiplesPistas As Boolean
Public Property MultiplesPistas() As Boolean
Get
Return bMultiplesPistas
End Get
Set(ByVal Value As Boolean)
bMultiplesPistas = Value
End Set
End Property
End Class
La clase Video continúa teniendo tres propiedades, pero nuestra nueva clase DVD ahora tiene cuatro (las tres de la clase Video y la nueva MultiplesPistas que agregamos).
Probando
Vamos a ver como funciona esto escribiendo el siguiente código en el Module1 que está en el proyecto:
Module Module1
Sub Main()
Dim basicVideo As New Video()
With basicVideo
.ID = "12345"
.Titulo = "Video Básico"
.Precio = 15
End With
Dim dvdVideo As New DVD()
With dvdVideo
.ID = "54321"
.Titulo = "Spiderman"
.Precio = 7
.MultiplesPistas = true
End With
Console.WriteLine("Video Básico")
With basicVideo
Console.WriteLine(" ID: {0}", .ID)
Console.WriteLine(" Título: {0}", .Titulo)
Console.WriteLine(" Precio: {0}", .Precio)
End With
Console.WriteLine("DVD")
With dvdVideo
Console.WriteLine(" ID: {0}", .ID)
Console.WriteLine(" Título: {0}", .Titulo)
Console.WriteLine(" Precio: {0}", .Precio)
Console.WriteLine(" Múltiples Pistas: {0}", .MultiplesPistas)
End With
Console.Read()
End Sub
End Module
Este código simplemente muestra como pueden ser usadas las clases Video y DVD, y confirma que nuestra clase DVD en verdad ha heredado los métodos de la clase Video. Primero creamos y cargamos con datos un objeto de tipo Video, y luego hacemos lo propio con la clase DVD, haciendo uso de los métodos heredados. Por último usamos el método Console.WriteLine para volcar los valores contenidos en las propiedades de ambos objetos y la línea Console.ReadLine se encarga de esperar a que se pulse una tecla antes de finalizar la ejecución del programa (algo muy útil cuando se está corriendo un programa desde el entorno de desarrollo).
Lo que hemos hecho hasta ahora es muy simple, heredamos de una clase base y la extendimos agregando un nuevo comportamiento. En muchos casos esto no es suficiente, ya que a menudo necesitamos no solo agregar nueva funcionalidad sino modificar o reemplazar por completo alguna ya existente en la clase base, y aquí en donde entran en juego los Overloads, Override y Shadows.
Sobrecarga de Métodos (Overload)
La sobrecarga de métodos nos permite que una clase tenga más de un método con el mismo nombre, siempre y cuando todos los métodos tengan diferentes parámetros. Lo que importa no es el nombre de los parámetros sino su tipo de dato, así como su cantidad. El conjunto de los tipos de dato de los parámetros de un método es lo que se conoce como firma del método (method signature).
En realidad, hasta ahora la sobrecarga de métodos no tiene nada que ver con la herencia, pero estos dos conceptos pueden interactuar de formas muy interesantes. Bueno, vamos a ver como funciona la sobrecarga de métodos en el contexto de una única clase. Volvamos al código de nuestra clase Video y agreguemos lo siguiente:
Private intItemsComprados As Integer
Public ReadOnly Property ItemsComprados() As Integer
Get
Return intItemsComprados
End Get
End Property
Public Sub Comprar()
intItemsComprados += 1
End Sub
Este código simplemente agrega una nueva propiedad y un método a nuestra clase base. El método agregado nos permite especificar que hemos comprado una nueva copia de nuestro Video, pero es un poco limitante ya que solamente podemos comprar de a uno por vez. Para remediar esta situación podemos agregar otra versión del mismo método que reciba un parámetro indicando cuantas copias deseamos comprar:
Public Sub Comprar(ByVal Cantidad As Integer)
intItemsComprados += Cantidad
End Sub
Ahora tenemos dos implementaciones diferentes del método Comprar en nuestra clase y ambas tienen diferentes firmas (cantidad y tipo de parámetros) y por eso se nos permite que ambos métodos tengan el mismo nombre.
Si volvemos a nuestro código de prueba podemos modificarlo para reflejar estos cambios introducidos:
With basicVideo
.ID = "12345"
.Titulo = "Video Básico"
.Precio = 15
.Comprar()
.Comprar(8)
End With
Cuando se ejecuta el método Comprar sin parámetros se está invocando la primera implementación que suma uno a la cantidad comprada hasta el momento, la segunda invocación agregará la cantidad de 8 al total comprado hasta el momento ejecutando la segunda implementación de nuestro método.
With basicVideo
Console.WriteLine(" ID: {0}", .ID)
Console.WriteLine(" Título: {0}", .Titulo)
Console.WriteLine(" Precio: {0}", .Precio)
Console.WriteLine(" Ítems Comprados: {0}", .ItemsComprados)
End With
Con esto mostramos como funciona la sobrecarga de métodos en una clase única, ahora vamos a ver como podemos aprovechar esto usando herencia.
Sobrecarga de Métodos en una Subclase
Recordemos que nuestra clase DVD hereda ambas implementaciones de nuestro método Comprar, pero ahora necesitamos agregar otra versión de este método para tener en cuenta que el precio del producto puede variar según un descuento especial del cliente.
Vayamos al código de la clase DVD y agreguemos lo siguiente:
Public Overloads Sub Comprar(ByVal Descuento As Single)
Precio = Precio - Descuento
MyBase.Comprar()
End Sub
Esto se empieza a poner interesante. Lo que primero podemos ver es que el método incluye la palabra Overloads. Esto es obligatorio para cualquier método de una subclase que hace una sobrecarga de un método de una clase base. Podemos no incluir esta palabra clave en la definición del método, pero en ese caso se asume el uso de Shadows, cuya función veremos mas adelante. Por el momento es importante saber que ambas opciones nos van a provocar cambios radicales de comportamiento en nuestra subclase.
Recordemos que para poder hacer una sobrecarga de un método es necesario que tengan diferente firma de parámetros. Si esto no es así, no será posible realizar la sobrecarga.
Por último, observemos la línea que usa la palabra clave MyBase:
MyBase.Comprar()
Esta línea de código ejecuta el método Comprar que está implementado en la clase base. La palabra MyBase es reservada del lenguaje y siempre representa a la clase base dentro de una subclase, y puede utilizarse en cualquier parte de la subclase para invocar métodos de la clase base ignorando la implementación propia de la subclase.
Para ver como funciona este nuevo método podemos modificar el código del Module1 para ver el resultado:
With dvdVideo
.ID = "54321"
.Titulo = "Spiderman"
.Precio = 7
.MultiplesPistas = true
.Comprar(2.0!)
End With
Al hacer esto, automáticamente estamos realizando una compra y a la vez modificando el precio de venta. Al mostrar el resultado de nuestra operación podemos confirmar que el precio de compra ha sido modificado.
Hasta ahora pudimos ver como podemos extender una clase base con nuevos métodos que hacen sobrecarga de métodos existentes en la clase base y que se implementan totalmente en la subclase. Sin embargo, algunas veces es necesario alterar o reemplazar por completo la funcionalidad de un método de la clase base y no agregar nuevas implementaciones. Para poder hacer esto necesitamos usar Override o Shadows, como veremos a continuación.
Reemplazando Métodos con Override
El diseñador de una clase base es responsable de pensar por adelantado los posibles usos que otros desarrolladores puedan darle usando herencia. En particular, el diseñador de una clase debe decidir si un método en particular puede ser alterado, reemplazado o nada de esto.
Si no se especifica lo contrario, los métodos no pueden ser alterados o reemplazados utilizando Override. Los métodos siempre pueden reemplazarse utilizando Shadows, lo que veremos con mas detalle mas tarde, pero la forma preferida de alterar o reemplazar un método base es utilizando Override ya que lo estamos haciendo con el permiso (y conocimiento) del autor de la clase original.
Para especificar que un método puede ser alterado o reemplazado utilizando Override debemos marcarlo con el modificador Overridable.
Por ejemplo, si el autor de la clase base Video prevé que en el futuro podría ser necesario implementar un sistema de precios distinto al actual, podría marcar la propiedad Precio con Overridable para permitir explícitamente este comportamiento:
Public Overridable Property Precio() As Single
Get
Return sngPrecio
End Get
Set(ByVal Value As Single)
sngPrecio = Value
End Set
End Property
Esta modificación no implica ningún cambio de funcionalidad en las clases Video o DVD, pero significa que la subclase DVD puede implementar su propia versión de la propiedad Precio si es necesario. Por ejemplo, podríamos modificar (aumentar, obviamente) el precio de un DVD si este tiene múltiples pistas de audio, y sería de esta manera:
Public Overrides Property Precio() As Single
Get
If MultiplesPistas Then
Return MyBase.Precio* 2
Else
Return MyBase.Precio
End If
End Get
Set(ByVal Value As Single)
MyBase.Precio = Value
End Set
End Property
Acuérdense que es necesario declarar el método como Overridable para poder reemplazarlo con Override. Si no usamos Override el nuevo método usará Shadows sobre el método original.
En la implementación nueva de la propiedad Precio podemos ver que no hemos reemplazado completamente la funcionalidad original sino que se ha alterado. El código nuevo hace uso de la palabra clave MyBase para ejecutar el método de la clase base y aprovechar el código existente, de hecho, el almacenamiento del valor se hace por completo en la clase base (no hay variable privada que corresponda a la propiedad y almacene su valor).
Set(ByVal Value As Single)
MyBase.Precio = Value
End Set
Aquí no estamos modificando funcionalidad para nada, de hecho estamos delegando totalmente la llamada a la base clase. Solamente se está modificando el comportamiento de la clase base al momento de obtener el valor de la propiedad Precio. EL código ahora verifica si el DVD tiene múltiples pistas de audio y si esto es así multiplica el precio por dos (oferta).
Hasta aquí pudimos ver como usar Overrides para alterar el comportamiento de una clase base. Si hubiéramos querido reemplazar completamente el método sería necesario evitar la llamada a la clase base (MyBase) y proveer la funcionalidad de almacenamiento interno del valor que estamos necesitando. Hay que tener cuidado y hacer muchas pruebas antes de reemplazar totalmente un método ya que esto puede tener consecuencias indeseadas en el comportamiento de la subclase. Es muy posible que este método interactúe con otros de la clase base y obviamente las cosas no van a funcionar como esperamos.
Mucha de la responsabilidad de asegurarse que el funcionamiento de un método marcado con Overridable es del diseñador de la clase base. De ser así, debe asumirse que alguna subclase reemplazará totalmente un método y entonces la clase base debe diseñarse teniendo esto en cuenta.
Ejecutando los nuevos Métodos
El concepto de alterar o reemplazar con Override un método es bastante simple. El diseñador de la clase decide cuales métodos puede ser alterados o reemplazados y permite a los diseñadores de las subclases el utilizar esta funcionalidad.
Las cosas se complican un poco cuando queremos ejecutar alguno de estos métodos alterados en una subclase. Antes de que nos podamos dar cuenta del porque esto es complicado necesitamos entender que existe una enorme diferencia entre el tipo de una Variable y el tipo de un Objeto.
Típicamente declaramos las variables que usamos del mismo tipo que el objeto que representan, por ejemplo:
Dim myDVD As New DVD()
Esta variable es de tipo DVD, y el objeto al que apunta la variable es de tipo DVD. Para aclarar un poco las cosas, la línea de arriba se puede escribir como:
Dim myDVD As DVD = New DVD()
Cuando usamos herencia podemos elegir declarar nuestra variable como del tipo de la clase base en lugar del objeto real a utilizar. Esto es muy útil ya que podemos escribir código genérico que funcione sin importarnos el tipo real del objeto que estamos manipulando (esta es una de las características de la programación OO que se conoce como Polimorfismo). Esto funciona porque todas las subclases que deriven de Video tienen las mismas propiedades básicas como ID, Titulo y Precio. Para hacer esto podemos declarar nuestra variable para que sea del tipo Video, pero aún así contendrá a un objeto de tipo DVD.
Dim myVideo As Video = New DVD()
Mientras que el objeto es realmente un DVD, nosotros lo vemos como un Video porque la variable que lo contiene ha sido declarada con este tipo, y solamente podemos acceder las propiedades y métodos de un objeto Video.
Agreguemos el siguiente código a nuestro Module1:
Dim myVideo As Video = New DVD()
With myVideo
.ID = "54321"
.Titulo = "Robocop III"
.Precio = 7
End With
CType(myVideo, DVD).MultiplesPistas = True
CType(myVideo, DVD).Comprar(2.0!)
Este código carga los valores de las propiedades ID, Titulo y Precio. Hasta aquí no hay sorpresas ya que estos métodos son parte de la clase base. Estas líneas de código van a funcionar bien con cualquier objeto de tipo Video o que derive del mismo.
El Polimorfismo es la capacidad de utilizar el mismo código con objetos de diferente tipo, y es algo muy útil.
Las últimas dos líneas son mas intrigantes porque estamos usando la función CType() para poder ver temporalmente a la variable myVideo como de tipo DVD, y así poder ver la propiedad MultiplesPistas y ejecutarle el método sobrecargado Comprar que recibe un parámetro de tipo Single. Obviamente estas líneas solamente funcionan si el objeto es realmente del tipo DVD (o uno derivado de este), de otra manera la conversión fallaría.
Entonces, ¿Qué pasa con el método Precio que alteramos? Aquí la pregunta realmente es ¿Qué implementación de este método se va a invocar si nuestra variable es del tipo Video y nuestro objeto es del tipo DVD? Para ver la respuesta escribamos el siguiente código a continuación del que agregamos antes:
Console.WriteLine("myVideo")
With myVideo
Console.WriteLine(" ID: {0}", .ID)
Console.WriteLine(" Título: {0}", .Titulo)
Console.WriteLine(" Precio: {0}", .Precio)
Console.WriteLine(" Ítems Comprados: {0}", .ItemsComprados)
End With
Si ejecutamos el código vamos a ver que el precio de nuestro video es 5 y no 7. Esto quiere decir que se ejecutó la implementación del objeto DVD!
Cuando un método es marcado con Overridable se convierte en un método virtual. Esto significa que sin importar el tipo de la variable que usemos siempre se va a ejecutar la implementación que dicta el tipo del objeto.
Para aclarar un poco las cosas veamos que ocurre con las diferentes combinaciones posibles entre tipos de objetos y variables:
|
Variable |
Objeto |
Método Invocado |
|
Clase Base |
Clase Base |
Clase Base |
|
Clase Base |
Subclase |
Subclase |
|
Subclase |
Subclase |
Subclase |
Esto es muy importante ya que podemos estar seguros de que al escribir código genérico como el mostrado más arriba aún se ejecutan los métodos especializados en las subclases, cosa que generalmente es lo deseado.
Esto último nos da el pié para discutir el comportamiento al usar Shadows y poder explicar el comportamiento diferente que posee.
Shadows y más Shadows
Hasta aquí vimos que podemos extender una clase base agregándole nuevos métodos. También hemos visto que podemos agregar nuevas versiones de métodos existentes utilizando sobrecarga (Overloads) y también que es posible alterar o reemplazar totalmente un método contando con el visto bueno del diseñador de la clase usando Override.
Ahora bien, ¿que sucede cuando necesitamos alterar o reemplazar un método existente y la clase base no nos da el permiso para hacerlo?
Si el método no está marcado con Overridable no nos es posible alterarlo o reemplazarlo. Probablemente esto sea una premisa de diseño y es obvio que el diseñador explícitamente no ha considerado la posibilidad de que una subclase pueda alterar o reemplazar el método en cuestión, así que es necesario pensarlo cuidadosamente y evaluar las consecuencias de ignorar una premisa de diseño de la base clase. Aún así, hay casos en lo que es necesario hacerlo sin importar las intenciones del diseñador original de la clase, o simplemente es diseño no es el correcto o adecuado para nuestras necesidades.
Otro escenario posible podría ser el de tener una aplicación versión 1.0 que hereda y extiende una clase base. Luego, la versión 2.0 de la clase base agrega un nuevo método que entra en conflicto con el nuevo método que nosotros agregamos en nuestra subclase. En vez de cambiar el nombre del método en nuestra subclase para evitar el conflicto, y rescribir el código que usa nuestra clase derivada, podemos simplemente elegir ignorar el nuevo método de la clase base y continuar utilizando en que existe en la subclase.
En ambos casos podemos usar el modificador Shadows para lograr nuestro objetivo reemplazando por completo el método de la clase base. Creo que la mejor forma de evaluar la funcionalidad y utilidad de esta posibilidad es a través de compararlo con lo que aprendimos de los otros modificadores Overloads y Override.
La sobrecarga de un método permite agregar una nueva variación de un método existente siempre y cuando el nuevo método tenga una firma diferente. El uso de Shadows completamente reemplaza al método y todas sus variantes de la clase base, dejando a la subclase con una única versión del método reemplazado (el que acabamos de crear). Usando este modificador no estamos extendiendo una interfaz, sino que estamos reemplazando totalmente un método existente y sus variantes.
A diferencia de usar Override, Shadows permite reemplazar un método de la clase base sin el visto bueno del diseñador, lo que trae aparejado un riesgo importante y además requiere la realización de profundas pruebas para asegurarnos de que todo funciona como esperamos. Hay que tener en cuenta que la clase base nunca fue pensada para hacer esto que nosotros vamos a hacer ahora.
Por otra parte, el uso de Shadows tiene un importante efecto secundario. Como dijimos antes el uso de Override genera métodos virtuales lo que significa que la implementación del método alterado o reemplazado que va a ser invocado depende del tipo del objeto que estamos usando y no del tipo de la variable que lo referencia. Cuando un método es reemplazado con Shadows no es virtual y entonces es el tipo de la variable el que dicta cual implementación del método será invocada. El tipo del objeto que referencia la variable es completamente ignorado.
Algunas Comparaciones Prácticas
Podemos apreciar las diferencias concretas de usar Overloads y Shadows modificando ligeramente el método Comprar de la subclase DVD para que use el segundo en lugar del primero:
Public Shadows Sub Comprar(ByVal Descuento As Single)
Precio = Precio - Descuento
MyBase.Comprar()
End Sub
El modificador Overloads ha sido reemplazado por Shadows, aunque a decir verdad este modificador no es técnicamente necesario ya que el comportamiento de Shadows es el que se aplica si no se especifica otro. Aún así es deseable incluirlo ya que de lo contrario recibiremos una advertencia del compilador especificando que el método está siendo reemplazado con Shadows en lugar de Override, y estamos explícitamente definiendo que este es el comportamiento buscado y no un error de tipeo.
Si ejecutamos nuestro nuevo código vemos que la utilización de MyBase aún funciona de la forma esperada. Típicamente, usaremos MyBase junto con Shadows teniendo en cuenta que el diseño de la clase base puede no soportar el que reemplacemos totalmente un método, y con el objetivo de aliviar los problemas que puedan surgir al eliminar por completo el código del método original y sus variaciones.
Porqué decimos “eliminar por completo el método original”? Porque esto es exactamente lo que ocurre en nuestra subclase. Hasta ahora podríamos haber escrito código como el siguiente:
Dim myDVD As New DVD()
With myDVD
.Comprar()
.Comprar(5)
.Comprar(2.0!)
End With
La clase DVD podía hacer uso de las dos implementaciones del método Comprar de su clase base usando herencia, además del nuevo que agregamos luego a nuestra subclase usando sobrecarga de métodos.
Ahora, al usar Shadows los métodos originales no existen más y solamente podemos utilizar el nuevo método que creamos para nuestra subclase. Las invocaciones a los métodos Comprar() y Comprar(5) no funcionan más, solamente Comprar(2.0!).
Shadows vs. Overrides
Para ver la diferencia de comportamiento entre estos dos modificadores de métodos vamos a modificar la implementación de la propiedad Precio de la clase DVD de la siguiente forma:
Public Shadows Property Precio() As Single
Get
If MultiplesPistas Then
Return MyBase.Precio* 2
Else
Return MyBase.Precio
End If
End Get
Set(ByVal Value As Single)
MyBase.Precio = Value
End Set
End Property
Hemos reemplazado el Overrides por Shadows como dijimos anteriormente, y al ejecutar el nuevo código podemos ver que el precio de nuestra variable myVideo es 12 y no 24 como en el caso anterior. Lo que ha pasado aquí es que el código modificado de la propiedad Precio no se ha ejecutado, en su lugar se ha ejecutado el código original de la clase base que no contemplaba el aumento de precio de los DVDs.
Como podemos ver este es el comportamiento opuesto al de los métodos virtuales que se logra con Overrides, quiere decir que se ha ejecutado el método que corresponde al tipo de la variable y no el del objeto que la misma referencia. Para dejar bien en claro el comportamiento de Shadows podemos consultar la siguiente tabla:
|
Variable |
Objeto |
Método Invocado |
|
Clase Base |
Clase Base |
Clase Base |
|
Clase Base |
Subclase |
Clase Base |
|
Subclase |
Subclase |
Subclase |
Obviamente es importante conocer este comportamiento y usarlo de forma acorde en nuestro código, ya que de otra forma podemos terminar con un profundo dolor de cabeza tratando de descifrar lo que pasa.
Por último, el uso de Shadows puede ser útil en algunos casos puntuales, pero siempre es mejor usar Overrides en la medida de lo posible ya que es la forma “legal” de hacer las cosas y evitarnos los problemas que surgen de utilizar una clase en contra de su diseño y funcionamiento interno, además de permitirnos usar las facilidades del polimorfismo de una forma mas natural e intuitiva.
Para Terminar
El nuevo Visual Basic.NET nos brinda todas las herramientas que necesitamos para implementar un diseño orientado a objetos. No solo tenemos herencia sino que tenemos a nuestra disposición otros conceptos relacionados como la sobrecarga de métodos y el alterado/reemplazo usando Overrides. Estos conceptos son muy importantes ya que nos permiten extender, alterar o reemplazar el comportamiento de una clase base a medida que las subclases lo necesiten.
También vimos que tenemos el nuevo y único modificador Shadows que también nos permite alterar o modificar métodos pero de una forma muy diferente. A diferencia del operador Overloads, el cual extiende la interfaz de una clase base, al usar Shadows estamos reemplazando completamente el método y sus variaciones de la base clase. También pudimos comprobar que a diferencia de Overrides, que nos permite alterar o reemplazar un método existente en la clase base con permiso de su diseñador, Shadows nos permite hacerlo en el caso de no contar con este permiso reemplazando totalmente el método y sus variantes de la clase base. Además, cuando usamos Overrides se crean métodos virtuales y cuando usamos Shadows no, por lo que la semántica de la ejecución de estos métodos alterados en una subclase cambia radicalmente.
Todas estas capacidades nos dan gran poder y flexibilidad al momento de diseñar y codificar nuestras aplicaciones, pudiendo tener grandes ventajas en la adaptabilidad a los cambios y reutilización de código que podemos obtener, y todos estos conceptos son increíblemente útiles cuando se los usa con inteligencia.
Andrés G Vettori
MCSE/MCSD/MCT
Líder de la Comunidad VB/C# del MUG Argentina
Referencias
Microsoft MSDN http://msdn.microsoft.com
Mastering Visual Basic.NET – Syngress
Visual Basic.NET Developers Guide - Syngress
A Programmers Introduction to Visual Basic.NET – SAMS
Visual Studio .NET Help Online