Buscador

Ocultamiento de miembros de una clase - II

Cuando en una clase hija creamos un método con el mismo nombre y parámetros que en la clase padre, el compilador realiza un ocultamiento implícito, aunque genera un aviso, recomendando que declaremos el método de la clase hija con Shadows. Veamos el Código fuente 274.

Public Class Empleado
'....
Public Sub Sueldo()
' aquí mostramos en consola el importe del sueldo formateado
Console.WriteLine("El sueldo es {0}", Format(Me.Salario, "#,#.##"))
Console.ReadLine()
End Sub
'....
End Class
Public Class Administrativo
'....
' si aquí no utilizáramos Shadows, el entorno
' marcaría este método con un aviso
Public Shadows Sub Sueldo()
' aquí incrementamos el valor actual de la propiedad Salario
Me.Salario += 250
End Sub
'....
End Class
Código fuente 274
Por otra parte, si aplicamos el ocultamiento en la sobre-escritura, el comportamiento del objeto se verá profundamente afectado. La mejor situación para comprobar este particular consiste en declarar una variable de la clase base y asignarle un objeto de una clase heredada. A pesar de que, como hemos comentado anteriormente, la sobre-escritura se basa en el enlace tardío, si ocultamos un miembro de la clase derivada, forzaremos al objeto a dirigirse a la versión de dicho miembro existente en la clase padre. 
El ejemplo del Código fuente 275 muestra este caso. En él creamos nuestras dos clases habituales, Empleado y Administrativo, relacionadas mediante herencia, con un método sobrescrito en ambas, que tiene la particularidad de que la versión existente en la clase derivada está oculto con Shadows. Esto hará que al instanciar un objeto de la clase hija, y pasárselo a una variable referenciada hacia la clase padre, la llamada al método sea desviada hacia la implementación existente en la clase padre, en lugar de a la clase derivada como sería su comportamiento habitual.

Module Module1
Sub Main()
Dim loPersona As Empleado
loPersona = New Administrativo()
loPersona.psNombre = "Juan"
' estamos utilizando sobre-escritura,
' por lo que el enlace tardío emplea el objeto
' que hay dentro de la variable y no la
' referencia de la variable;
' al estar oculta con Shadows la implementación
' del método MostrarNombre() en la clase Administrativo
' se ejecuta dicho método pero de la clase Empleado
loPersona.MostrarNombre()
Console.ReadLine()
End Sub
End Module
Public Class Empleado
Public psNombre As String
Public pdtFHAlta As Date
Public Overridable Sub MostrarNombre()
Console.WriteLine("El nombre del empleado es {0}", _
Me.psNombre)
End Sub
End Class
Public Class Administrativo : Inherits Empleado
' ocultamos este método
Public Shadows Sub MostrarNombre()
Console.WriteLine("Nombre del empleado")
Console.WriteLine("===================")
Console.WriteLine(UCase(Me.psNombre))
End Sub
End Class
Código fuente 275
La Tabla 24 muestra, al utilizar sobre-escritura y ocultamiento, la clase de la cuál será ejecutado el método, en función de la referencia de la variable y el tipo de objeto.

Ocultamiento de miembros de una clase - I

Esta técnica consiste en crear dentro de una clase derivada, miembros con el mismo nombre (y firma, en el caso de métodos) que los existentes en la clase base, pero ocultando el acceso a los miembros de la clase base para los objetos instanciados de la subclase. Utilizaremos la palabra clave Shadows, en la declaración de aquellos miembros a esconder.
Cuando aplicamos el ocultamiento sobre una subclase que tiene métodos sobrecargados en la clase base, dichos métodos sobrecargados quedarán inaccesibles para la clase derivada. Como ejemplo, en el Código fuente 273, la clase Empleado implementa dos versiones sobrecargadas del método Sueldo( ), mientras que la clase hija Administrativo también tiene el método Sueldo( ), pero al declararse con Shadows, impide que los objetos de tipo Administrativo ejecuten los métodos Sueldo( ) de la clase Empleado.

Module Module1
Sub Main()
Dim loAdmin As New Administrativo()
Dim ldbImporte As Double
Dim lsFecha As String
loAdmin.Salario = 925.86
ldbImporte = loAdmin.Sueldo(80, "Viajes")
' los siguientes métodos están ocultos
' desde este objeto y se produce un error al llamarlos
loAdmin.Sueldo()
lsFecha = loAdmin.Sueldo(5)
End Sub
End Module
Public Class Empleado
Private mdbSalario As Double
Public Property Salario() As Double
Get
Return mdbSalario
End Get
Set(ByVal Value As Double)
mdbSalario = Value
End Set
End Property
' métodos sobrecargados
Public Overloads Sub Sueldo()
' aquí mostramos en consola el importe del sueldo formateado
Console.WriteLine("El sueldo es {0}", Format(Me.Salario, "#,#.##"))
Console.ReadLine()
End Sub
Public Overloads Function Sueldo(ByVal liDia As Integer) As String
' aquí mostramos la fecha del mes actual
' en la que se realizará la transferencia
' del sueldo al banco del empleado
Dim ldtFechaActual As Date
Dim lsFechaCobro As String
ldtFechaActual = Now()
lsFechaCobro = CStr(liDia) & "/" & _
CStr(Month(ldtFechaActual)) & "/" & _
CStr(Year(ldtFechaActual))
Return lsFechaCobro
End Function
End Class
Public Class Administrativo
Inherits Empleado
' este método ensombrece/oculta a los sobrecargados
' de la clase base Empleado
Public Shadows Function Sueldo(ByVal ldbImporteIncentivo As Double, _
ByVal lsTipoIncentivo As String) As Double
' aquí calculamos la cantidad de incentivo
' que se añadirá al sueldo del empleado,
' en función del tipo de incentivo
Dim ldbIncentivo As Double
' según el tipo de incentivo,
' se descuenta un importe
' de la cantidad del incentivo
Select Case lsTipoIncentivo
Case "Viajes"
ldbIncentivo = ldbImporteIncentivo - 30
Case "Extras"
ldbIncentivo = ldbImporteIncentivo - 15
End Select
Return ldbIncentivo
End Function
End Class
Código fuente 273

La palabra clave MyClass

Ante una situación de sobre-escritura de métodos en una clase base y heredada, la palabra clave MyClass, nos permite, alterar el comportamiento por defecto en la llamada al método, forzando la ejecución del que existe en la clase base. Expliquemos mejor este aspecto con un ejemplo ilustrativo. 
Creamos la clase Empleado y su clase hija Administrativo, y en ambas escribimos el método VerFecha( ) utilizando sobre-escritura; este método va a ser llamado a su vez desde el método MostrarDatos( ), que se encuentra en la clase Empleado con la siguiente sintaxis.

Me.VerFecha()

Si desde el código cliente instanciamos un objeto de Administrativo, al ejecutar MostrarDatos( ), la versión de VerFecha( ) ejecutada será la que se encuentra en el código de la clase Administrativo. ¿Pero y si necesitamos ejecutar la versión de VerFecha( ) que está en el código de la clase Empleado?, pues es posible utilizando la palabra clave MyClass. Veamos este ejemplo al completo en el Código fuente 272.

Module Module1
Sub Main()
Dim oAdmin As New Administrativo()
oAdmin.Nombre = "Alfredo"
oAdmin.FechaAlta = DateTime.Today
oAdmin.MostrarDatos()
Console.ReadLine()
End Sub
End Module
Public Class Empleado
Public Nombre As String
Public FechaAlta As Date
Public Sub MostrarDatos()
Console.WriteLine("Datos del empleado")
Console.WriteLine("Nombre: {0}", Me.Nombre)
Me.VerFecha() ' ejecuta VerFecha de clase Administrativo
MyClass.VerFecha() ' ejecuta VerFecha de clase Empleado
End Sub
Public Overridable Sub VerFecha()
Console.WriteLine("Fecha alta completa: {0}", Me.FechaAlta)
End Sub
End Class
Public Class Administrativo
Inherits Empleado
Public Overrides Sub VerFecha()
Console.WriteLine("Fecha alta -sólo nombre mes- : {0}",
Me.FechaAlta.ToString("MMMM"))
End Sub
End Class
Código fuente 272
Como podemos suponer, MyClass fuerza a ejecutar la implementación del método que existe en la clase desde la que se hace la llamada a dicho método.

Diferencias entre sobrecarga y sobre-escritura en base al tipo de enlace - II

Pasando seguidamente a la sobre-escritura, escribiremos en la clase padre el método MostrarNombre( ), y lo sobrescribiremos en la clase hija. Instanciaremos después un objeto Administrativo y lo asignaremos a la variable tipificada como Empleado. Debido a que el enlace tardío se basa en el tipo del objeto que contiene la variable, y no en el tipo de la variable, al llamar al método MostrarNombre( ), se ejecutará la versión de la clase Administrativo. Veamos el Código fuente 271.

Module Module1
Sub Main()
Dim loPersona As Empleado
loPersona = New Administrativo()
loPersona.psNombre = "Juan García"
' como la sobre-escritura utiliza enlace tardío,
' se basa en el objeto que contiene la variable y
' no en el tipo de dato de la variable, se ejecuta
' la versión del método MostrarNombre() que está
' en la clase Administrativo, ya que el objeto
' que contiene la variable loPersona es una
' instancia de Administrativo
loPersona.MostrarNombre()
Console.ReadLine()
End Sub
End Module
Public Class Empleado
Public psNombre As String
Public pdtFHAlta As Date
Public Overridable Sub MostrarNombre()
Console.WriteLine("El nombre del empleado es {0}", _
Me.psNombre)
End Sub
End Class
Public Class Administrativo : Inherits Empleado
Public Overrides Sub MostrarNombre()
Console.WriteLine("Nombre del empleado")
Console.WriteLine("===================")
Console.WriteLine(UCase(Me.psNombre))
End Sub
End Class
Código fuente 271
La Tabla 23 muestra, al utilizar sobre-escritura, la clase de la cuál será ejecutado el método, en función de la referencia de la variable y el tipo de objeto.
Tabla 23. Método ejecutado mediante enlace tardío bajo sobre-escritura.
Debido al hecho de que los miembros sobrescritos emplean enlace tardío, otra de las denominaciones que se utiliza para ellos es la de método virtual.

Diferencias entre sobrecarga y sobre-escritura en base al tipo de enlace - I

La otra diferencia entre sobrecarga y sobre-escritura consiste en el tipo de enlace que utilizan. Mientras que la sobrecarga se basa en enlace temprano, la sobre-escritura emplea enlace tardío. 
El mejor modo de comprobar este punto, consiste en declarar una variable con un tipo perteneciente a una clase base, pero asignándole un objeto correspondiente a una clase heredada. 
Por ejemplo, en el caso de la sobrecarga, creamos las ya conocidas clases Empleado y Administrativo, escribiendo el método VerAlta( ), sobrecargado en cada una de ellas.
A continuación declaramos una variable de tipo Empleado, pero instanciamos un objeto de la clase Administrativo y lo asignamos a la variable. Debido a que el enlace temprano se basa en el tipo de la variable y no en el objeto que contiene, el método VerAlta( ) al que podremos acceder será el que se encuentra en la clase Empleado. Veamos el Código fuente 270.

Module Module1
Sub Main()
Dim loPersona As Empleado
loPersona = New Administrativo()
loPersona.psNombre = "Juan García"
loPersona.pdtFHAlta = "15/1/2002"
' como la sobrecarga utiliza enlace temprano,
' se basa en el tipo de la variable y no
' en el objeto que se asigna a esa variable,
' por ello sólo es visible la implementación
' del método que hay en la clase Empleado
loPersona.VerAlta()
' si intentamos ejecutar el método VerAlta()
' que recibe una cadena, se producirá el siguiente error:
' "Demasiados argumentos para 'Public Sub VerAlta()'."
loPersona.VerAlta("Mes") ' <-- error
Console.ReadLine()
End Sub
End Module
Public Class Empleado
Public psNombre As String
Public pdtFHAlta As Date
' mostrar la fecha de alta al completo
Public Sub VerAlta()
Console.WriteLine("El empleado {0} se incorporó el {1}", _
Me.psNombre, Me.pdtFHAlta)
End Sub
End Class
Public Class Administrativo : Inherits Empleado
' mostrar sólo una parte de la fecha de alta
' según el parámetro pasado
Public Overloads Sub VerAlta(ByVal lsParteFecha As String)
Dim lsValorFecha As String
Select Case lsParteFecha
Case "Mes"
lsValorFecha = Format(Me.pdtFHAlta, "MMMM")
Case "DiaSemana"
lsValorFecha = Format(Me.pdtFHAlta, "dddd")
End Select
Console.WriteLine("Empleado {0}", Me.psNombre)
Console.WriteLine("Incorporado {0}", lsValorFecha)
End Sub
End Class
Código fuente 270

Herencia y sobre-escritura de métodos - III

Debido a cuestiones de diseño, en algunas ocasiones precisaremos que al mismo tiempo que sobrescribimos un miembro dentro de una clase heredada, dicho miembro no pueda ser sobrescrito por las clases que hereden de esta. En estas situaciones, al declarar el miembro, usaremos la palabra clave NotOverridable.
Volvamos pues, al ejemplo de la clase Administrativo, en el que sobrescribíamos el método VerDatos(). Si cambiamos la declaración de dicho método por la mostrada en el Código fuente 269, una tercera clase Directivo, que heredase de Administrativo, no podría sobrescribir el mencionado método.

Public Class Administrativo : Inherits Empleado
' rescribimos este método totalmente usando Overrides
' e impedimos que pueda ser rescrito por clases
' derivadas de esta
Public NotOverridable Overrides Sub VerDatos()
Console.WriteLine("Datos del empleado")
Console.WriteLine("==================")
Console.WriteLine("Código: {0}", Me.piID)
Console.WriteLine("Nombre: {0}", UCase(Me.Nombre))
End Sub
End Class
Public Class Directivo : Inherits Administrativo
' se produce un error, no se puede sobrescribir este método
' ya que la clase Administrativo lo impide con NotOverridable
Public Overrides Sub VerDatos()
'.....
'.....
End Sub
End Class
Código fuente 269

No podemos utilizar NotOverridable en métodos de una clase base, ya que la misión de este modificador es impedir la sobre-escritura de miembros en clases derivadas, pero desde una clase que a su vez también ha sido derivada desde la clase base. Si no queremos, en una clase base, que un método pueda ser sobrescrito, simplemente no utilizamos en su declaración la palabra clave Overridable.

Herencia y sobre-escritura de métodos - II

Para ilustrar esta situación, añadiremos a la clase Empleado la propiedad Salario, y un método para calcularlo, de modo que todos los empleados tengan inicialmente el mismo salario, sin embargo, los administrativos necesitan un pequeño incremento. Para no tener que volver a realizar el cálculo en la clase Administrativo, vamos a aprovechar el cálculo que ya se realiza en la clase padre, añadiendo sólo las operaciones particulares que necesitemos. Veámoslo en el Código fuente 268.

Module Module1
Sub Main()
Dim loEmp As New Empleado()
loEmp.piID = 50
loEmp.Nombre = "juan casas"
loEmp.VerDatos()
loEmp.CalcularSalario()
Console.WriteLine("Salario {0}", loEmp.Salario)
Console.WriteLine()
Dim loAdmin As New Administrativo()
loAdmin.piID = 129
loAdmin.Nombre = "elena redondo"
loAdmin.VerDatos()
loAdmin.CalcularSalario()
Console.WriteLine("Salario {0}", loAdmin.Salario)
Console.ReadLine()
End Sub
End Module
Public Class Empleado
'......
'......
Public miSalario As Integer
Public Property Salario() As Integer
Get
Return miSalario
End Get
Set(ByVal Value As Integer)
miSalario = Value
End Set
End Property
Public Overridable Sub CalcularSalario()
Me.Salario = 800
End Sub
'......
'......
End Class
Public Class Administrativo : Inherits Empleado
'......
'......
Public Overrides Sub CalcularSalario()
' utilizamos el método de la clase base
MyBase.CalcularSalario()
Me.Salario += 50
End Sub
End Class
Código fuente 268

Herencia y sobre-escritura de métodos - I

Esta técnica consiste en la capacidad de crear, en una clase derivada, un método que altere parcial o totalmente, la implementación ya existente de dicho método en la clase base. Una de las diferencias existentes con la sobrecarga de métodos, estriba en que al sobrescribir, el método en la subclase puede tener el mismo nombre y lista de parámetros que el ya existente en la clase padre. Podemos sobrescribir tanto métodos como propiedades. 
Para indicar en la clase base que un método podrá ser sobrescrito en una subclase, debemos declarar dicho método utilizando la palabra clave Overridable. Posteriormente, cuando en una clase derivada queramos rescribir el método de la clase base, lo declararemos empleando la palabra clave Overrides. Podemos deducir por lo tanto, que la reescritura de métodos es un proceso que se debe realizar con el consentimiento previo de la clase base. 
El Código fuente 267 muestra un ejemplo del uso de este tipo de métodos. En él creamos las ya conocidas clase base Empleado y subclase Administrativo, y en ambas escribimos el método VerDatos( ), con la particularidad de que en la clase hija, cambiamos totalmente su implementación.

Module Module1
Sub Main()
Dim loEmp As New Empleado()
loEmp.piID = 50
loEmp.Nombre = "juan casas"
loEmp.VerDatos()
Console.WriteLine()
Dim loAdmin As New Administrativo()
loAdmin.piID = 129
loAdmin.Nombre = "elena redondo"
loAdmin.VerDatos()
Console.ReadLine()
End Sub
End Module
Public Class Empleado
Public piID As Integer
Private msNombre As String
Public Property Nombre() As String
Get
Return msNombre
End Get
Set(ByVal Value As String)
msNombre = Value
End Set
End Property
' marcamos el método como rescribible con Overridable
Public Overridable Sub VerDatos()
Console.WriteLine("Datos del empleado: {0}-{1}", _
Me.piID, Me.Nombre)
End Sub
End Class
Public Class Administrativo : Inherits Empleado
' rescribimos este método totalmente usando Overrides
Public Overrides Sub VerDatos()
Console.WriteLine("Datos del empleado")
Console.WriteLine("==================")
Console.WriteLine("Código: {0}", Me.piID)
Console.WriteLine("Nombre: {0}", UCase(Me.Nombre))
End Sub
End Class
Código fuente 267

Pero, ¿qué sucede si queremos utilizar la implementación del método base en la clase derivada?, pues sólo necesitamos llamar al método de la clase padre usando la palabra clave MyBase.

MyBase, acceso a los métodos de la clase base

Esta palabra clave proporciona acceso a los miembros de una clase base desde su correspondiente subclase. 
Siguiendo con el ejemplo de la sobrecarga descrito en el apartado anterior, supongamos que para calcular los incentivos de un administrativo, queremos en primer lugar, realizar la misma operación que hacemos con los empleados base, y después, un cálculo específico para el administrativo. En tal caso, modificaremos el método CalcularIncentivos( ) en la clase Administrativo, añadiéndole una llamada al mismo método de la clase padre. Veamos el Código fuente 265.

Public Class Administrativo
Inherits Empleado
Public Overloads Sub CalcularIncentivos(ByVal liHoras As Integer)
' llamamos a la clase base con MyBase para hacer
' en primer lugar los mismos cálculos de incentivos
' de la clase Empleado
MyBase.CalcularIncentivos()
' después se hacen los cálculos propios de
' esta clase
Me.piIncentivos += liHoras * 15
End Sub
End Class
Código fuente 265

Al utilizar MyBase, no es obligatorio llamar desde el método en la clase hija, a su misma versión en la clase padre; podríamos perfectamente en el ejemplo anterior, haber llamado desde el método CalcularIncentivos( ) de la clase Administrativo, al método VerIncentivos( ) de la clase Empleado, todo depende de los requerimientos del diseño de la clase. Ver Código fuente 266.

MyBase.VerIncentivos()
Código fuente 266

Herencia y sobrecarga de métodos

Podemos sobrecargar métodos existentes en una clase base dentro de una clase derivada, para ello simplemente escribimos la implementación del método sobrecargado utilizando la palabra clave Overloads, tal y como se ha explicado en anteriores apartados. 
Tomemos como ejemplo una clase base Empleado y su clase derivada Administrativo. Cuando calculamos los incentivos para un empleado, lo hacemos basándonos en una operación sobre el salario; sin embargo, los incentivos para el administrativo se calculan en base a un número de horas, por lo que escribimos dos implementaciones del mismo método en cada clase, sobrecargando el método en la clase Administrativo, como muestra el Código fuente 264.

Module Module1
Sub Main()
Dim loEmp As Empleado = New Empleado()
loEmp.psNombre = "Ana Gómez"
loEmp.piSalario = 2000
loEmp.CalcularIncentivos()
loEmp.VerIncentivos()
Dim loAdmin As New Administrativo()
loAdmin.psNombre = "Jorge Peral"
loAdmin.piSalario = 1600
loAdmin.CalcularIncentivos(10)
loAdmin.VerIncentivos()
Console.ReadLine()
End Sub
End Module
Public Class Empleado
Public piID As Integer
Public psNombre As String
Public piSalario As Integer
Public piIncentivos As Integer
' calcular los incentivos en base
' al salario
Public Sub CalcularIncentivos()
Me.piIncentivos = Me.piSalario / 10
End Sub
Public Sub VerIncentivos()
Console.WriteLine("Los incentivos de {0} son {1}", _
Me.psNombre, Me.piIncentivos)
End Sub
End Class
Public Class Administrativo
Inherits Empleado
' calcular los incentivos en base a horas
Public Overloads Sub CalcularIncentivos(ByVal liHoras As Integer)
Me.piIncentivos = liHoras * 15
End Sub
End Class
Código fuente 264
Hemos de aclarar que si no utilizáramos la palabra clave Overloads en la subclase, el programa también se ejecutaría, pero obtendríamos un aviso del compilador advirtiéndonos de la situación. Este mensaje lo podemos ver utilizando la ventana Lista de tareas, que emplea el IDE para mostrar los errores y avisos del compilador. Ver Figura 120.
Figura 120. Lista de tareas y editor de código mostrando aviso del compilador.

Protected Friend

Los miembros de clase declarados al mismo tiempo con Protected y Friend, obtendrán una combinación de ambos modificadores; por lo tanto, serán accesibles desde el código de su clase, clases derivadas, y por todo el código que se encuentre dentro de su ensamblado.

Friend - II

A continuación, agregamos una referencia desde el proyecto de consola hacia el proyecto de biblioteca de clases, y en el módulo de código, importamos el espacio de nombres del ensamblado correspondiente a la biblioteca de clases, escribiendo después en Main( ), código que interactúe con un objeto de la clase Empleado, comprobaremos cómo no es posible manipular los miembros Friend del objeto Empleado. Ver Código fuente 263.

Imports ClassLibrary1
Module Module1
Sub Main()
Dim loEmplea As Empleado = New Empleado()
' al acceder a las propiedades del objeto
' desde este proyecto, no está disponible
' el miembro mdbSueldo ya que está declarado
' como Friend en la clase Empleado
loEmplea.piID = 70
loEmplea.Nombre = "Alicia Mar"
loEmplea.VerDatos()
Console.Read()
End Sub
End Module
Código fuente 263

Aunque hemos descrito su modo de manejo a través de clases, la palabra clave Friend también puede ser utilizada como modificador de ámbito para variables y procedimientos situados en módulos de código.

Friend - I

Un miembro de clase declarado con este modificador, será accesible por todo el código de su proyecto o ensamblado. 
Para poder comprobar el comportamiento utilizando el ámbito Friend, debemos crear una solución formada por un proyecto de tipo consola y uno de biblioteca de clases; en este último escribimos una clase definiendo alguno de sus miembros con este modificador, como vemos en el Código fuente 262.

Public Class Empleado
Public piID As Integer
Private msNombre As String
' esta variable sólo puede ser
' accesible por tipos que estén
' dentro de este ensamblado
Friend mdbSueldo As Double
Public Property Nombre() As String
Get
Return msNombre
End Get
Set(ByVal Value As String)
msNombre = Value
End Set
End Property
Public Sub VerDatos()
Console.WriteLine("Datos del empleado")
Console.WriteLine("Código: {0}", Me.piID)
Console.WriteLine("Nombre: {0}", Me.msNombre)
Console.WriteLine("Sueldo: {0}", Me.mdbSueldo)
End Sub
End Class
Public Class Plantilla
Public Sub Analizar()
Dim loEmp As Empleado = New Empleado()
loEmp.piID = 50
loEmp.Nombre = "Francisco Perea"
' desde esta clase sí podemos acceder
' al miembro mdbSueldo del objeto
' Empleado, ya que estamos en el mismo ensamblado
loEmp.mdbSueldo = 450
loEmp.VerDatos()
End Sub
End Class
Código fuente 262