Buscador

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

Protected

Un miembro de clase declarado con este modificador, será accesible desde el código de su propia clase y desde cualquier clase heredada. El Código fuente 261 muestra un ejemplo del uso de Protected

Module Module1
Sub Main()
' con una instancia del objeto Empleado o Administrativo
' no podemos acceder al método VerFecha()
' ya que es Protected
Dim loEmp As Empleado = New Empleado()
loEmp.psNombre = "Pedro Peral"
Dim loAdmin As New Administrativo()
loAdmin.piID = 567
loAdmin.psNombre = "Juan Iglesias"
loAdmin.pdtFecha = "5/9/2002"
loAdmin.AsignarDNI("11223344")
loAdmin.DatosAdmin()
Console.Read()
End Sub
End Module
Public Class Empleado
Public psNombre As String
Public pdtFecha As Date
' los dos siguientes miembros sólo serán visibles
' dentro de esta clase o en sus clases derivadas
Protected psDNI As String
Protected Function VerFecha()
Return pdtFecha
End Function
Public Sub AsignarDNI(ByVal lsDNI As String)
' desde aquí sí tenemos acceso a la variable
' Protected declarada en la clase
Me.psDNI = lsDNI
End Sub
End Class
Public Class Administrativo
Inherits Empleado
Public piID As Integer
Public Sub DatosAdmin()
Console.WriteLine("Datos del administrativo")
Console.WriteLine("Identificador: {0}", Me.piID)
Console.WriteLine("Nombre: {0}", Me.psNombre)
' desde esta clase derivada sí tenemos acceso
' a lo miembtos Protected de la clase padre
Console.WriteLine("Fecha: {0}", Me.VerFecha())
Console.WriteLine("DNI: {0}", Me.psDNI)
End Sub
End Class
Código fuente 261

Reglas de ámbito específicas para clases

Las normas de ámbito que ya conocemos, establecen que cuando declaramos un miembro de clase con el modificador de ámbito Public, dicho elemento será accesible por todo el código de la clase, clases heredadas y código cliente; mientras que si lo declaramos con Private, ese miembro sólo será accesible por el código de la propia clase. Veamos el Código fuente 260.

Module General
Sub Main()
Dim loUsu As Usuario
loUsu = New Usuario()
' accedemos al método público del objeto
loUsu.AsignarNombre("Daniel")
End Sub
End Module
Public Class Usuario
' esta variable sólo es accesible
' por el código de la propia clase
Private msNombre As String
' este método es accesible desde cualquier punto
Public Sub AsignarNombre(ByVal lsValor As String)
msNombre = lsValor
End Sub
End Class
Public Class Operador
Inherits Usuario
Public Sub New()
' accedemos a un método público
' de la clase base
Me.AsignarNombre("Alfredo")
End Sub
End Class
Código fuente 260

En el anterior fuente, la variable msNombre de la clase Usuario, declarada privada a nivel de clase, sólo es manipulada por los métodos de la propia clase. Por otro lado, el método AsignarNombre( ), al declararse público, es utilizado desde clases heredades y código cliente. Además de estas normas, ya conocidas, disponemos de los modificadores descritos en los siguientes apartados, diseñados para resolver problemas concretos de ámbito entre clases.

Todas las clases necesitan una clase base

El diseño de la plataforma .NET Framework dicta como norma que toda clase creada necesita heredar de una clase base. Esto puede resultar un tanto confuso al principio, ya que en los ejemplos escritos en anteriores apartados, no hemos heredado, al menos aparentemente, de ninguna clase. 
Cuando creamos una nueva clase, si en ella no establecemos una relación explícita de herencia con otra clase, el entorno de ejecución de .NET internamente la creará haciendo que herede de la clase Object, que se encuentra en el espacio de nombres System. 
Esto es debido a que el tipo de herencia en .NET Framework es simple, y en la jerarquía de clases de la plataforma, Object es la clase base, a partir de la cuál, se derivan el resto de clases. Por este motivo, las declaraciones mostradas en el Código fuente 259 serían equivalentes.

' declaración normal (se hereda implícitamente de Object)
Public Class Empleado
' declaración heredando explícitamente de Object
Public Class Empleado
Inherits System.Object
Código fuente 259

Herencia

Este es uno de los aspectos más importantes, sino el más importante, en un lenguaje orientado a objetos. Empleando la herencia podemos crear una clase base o padre, con especificaciones generales, y a partir de ella, crear nuevas clases derivadas o hijas. En el momento de declarar una clase derivada, y sin haber escrito más código, ya tenemos acceso a todos los miembros de la clase base; posteriormente, podemos escribir código adicional en la clase derivada para ampliar sus funcionalidades. 
Una clase hija puede servir a su vez como clase base para la creación de otra clase derivada, y así sucesivamente, con lo que podemos componer nuestra propia jerarquía con la estructura de clases que necesitemos. 
Para crear una clase derivada, debemos declarar una nueva clase, especificando cuál es su clase base mediante la palabra clave Inherits. En el Código fuente 256 se muestran los dos modos disponibles de crear una clase heredada.

' crear clase derivada en dos líneas
Public Class Administrativo
Inherits Empleado
' crear clase derivada en la misma línea
Public Class Administrativo : Inherits Empleado
Código fuente 256

Una vez que creemos la clase derivada, tendremos a nuestra disposición todos los elementos de la clase base, tanto desde la propia clase, como desde el código cliente.
Por ejemplo, supongamos que se nos plantea la necesidad de crear un tipo de empleado, con características más especializadas de las que ahora tiene nuestra clase Empleado. Podemos optar por modificar toda la implementación ya existente para la clase Empleado, lo que afectaría al código cliente que ya utiliza dicha clase, y seguramente de forma negativa; o bien, podemos crear una nueva clase, que herede de Empleado, y en la que definiríamos el comportamiento de este nuevo tipo de empleado. A esta nueva clase le daremos el nombre Administrativo, y su código podemos verlo en el Código fuente 257.

Public Class Administrativo
Inherits Empleado
Public Sub EnviarCorreo(ByVal lsMensaje As String)
Console.WriteLine("Remitente del mensaje: {0} {1}", _
Me.Nombre, Me.Apellidos)
Console.WriteLine("Texto del mensaje: {0}", lsMensaje)
Console.ReadLine()
End Sub
End Class
Código fuente 257

Constructores y herencia - III

Combinando las características de métodos constructores junto a las de sobrecarga, podemos crear un conjunto de constructores sobrecargados para la clase. Ver el Código fuente 255.

Public Class Empleado
Public psNombre
Public psApellidos
Public psCiudad
Private mdtFechaCrea
' en este constructor sin parámetros,
' asignamos la fecha actual
Public Sub New()
mdtFechaCrea = Now()
End Sub
' en este constructor, asignamos valores
' a todos los campos de la clase
Public Sub New(ByVal lsNombre As String, _
ByVal lsApellidos As String, ByVal lsCiudad As String)
psNombre = lsNombre
psApellidos = lsApellidos
psCiudad = lsCiudad
End Sub
End Class
Código fuente 255

Constructores y herencia - II

Al igual que ocurre en un método normal, New( ) admite parámetros; esto nos sirve para asignar valores de inicio al objeto en el momento de su instanciación. La denominación para este tipo de métodos es constructor parametrizado. El Código fuente 254 nos muestra una variación del fuente anterior, utilizando un constructor de este tipo.

Module General
Sub Main()
Dim loEmp As Empleado
loEmp = New Empleado("5/7/2002")
Console.WriteLine("El objeto se ha creado el día {0}", loEmp.FechaCrea)
Console.ReadLine()
' este es otro modo de instanciar
' un objeto con un constructor parametrizado
Dim loEmp2 As New Empleado("08/4/2002")
End Sub
End Module
Public Class Empleado
Private mdtFechaCrea
Public Property FechaCrea() As Date
Get
Return mdtFechaCrea
End Get
Set(ByVal Value As Date)
mdtFechaCrea = Value
End Set
End Property
' método constructor con parámetro
Public Sub New(ByVal ldtFecha As Date)
' asignamos el valor del parámetro
' a una variable de propiedad
Me.FechaCrea = ldtFecha
End Sub
End Class
Código fuente 254

Constructores y herencia - I

Métodos constructores 
El primer método que es ejecutado al instanciar un objeto de la clase se denomina constructor. Este tipo de método resulta útil para tareas de configuración iniciales sobre el objeto. No es necesario escribir un método constructor en la clase, ya que en el caso de que no exista, el compilador se encarga de crearlo implícitamente. Para escribir nuestros propios constructores de clase, crearemos un método con el nombre New( ), como vemos en el Código fuente 253. En dicho ejemplo, al instanciarse un objeto de la clase Empleado, se asignará a una de sus propiedades la fecha actual.

Module General
Sub Main()
Dim loEmp As Empleado
loEmp = New Empleado()
Console.WriteLine("El objeto se ha creado el día {0}", loEmp.FechaCrea)
Console.ReadLine()
End Sub
End Module
Public Class Empleado
Private mdtFechaCrea As Date
Public Property FechaCrea() As Date
Get
Return mdtFechaCrea
End Get
Set(ByVal Value As Date)
mdtFechaCrea = Value
End Set
End Property
' método constructor
Public Sub New()
' asignamos un valor inicial
' a una variable de propiedad
Me.FechaCrea = Now
End Sub
End Class
Código fuente 253

Facilitar el desarrollo a través de una solución multiproyecto

Para desarrollar el ejemplo descrito en los anteriores apartados, hemos tenido que abrir por separado cada uno de los proyectos que lo integran: por un lado el de librería, y por otro el de consola.El IDE de VS.NET nos facilita el trabajo con varios proyectos a la vez, pudiéndolos tener todos abiertos en una misma sesión de trabajo, a través de una única solución contenedora. Si nos encontramos con una situación como la del ejemplo anterior, en primer lugar podemos crear el proyecto de la librería, escribir su código y compilarla para obtener el archivo DLL. A continuación, seleccionaremos la opción de menú del IDE Archivo + Agregar proyecto + Nuevo proyecto, agregando un proyecto de tipo consola. Esta acción añadirá este último proyecto a la solución, aunque debemos tener presente que la referencia hacia la librería deberemos seguir estableciéndola nosotros manualmente. Finalmente, haremos clic derecho en el Explorador de soluciones sobre el nombre del proyecto de consola, y elegiremos la opción de menú Establecer como proyecto de inicio. De esta manera, el comienzo de la ejecución se realizará por este proyecto y no la librería, que como recordaremos no es directamente ejecutable. Podemos comprobar rápidamente cuál es el proyecto de inicio, ya que en el Explorador de soluciones aparece remarcado en negrita. Ver la Figura 119.
Figura 119. Solución con varios proyectos, y proyecto de inicio remarcado.

Crear espacios de nombres adicionales en una librería

Continuando con el ejemplo anterior, supongamos ahora, que en el código de la librería añadimos un nuevo espacio de nombres, al que llamamos Personal, y escribimos en su interior la clase Empleado, como vemos en el Código fuente 251.

' código de la clase Contabilidad
'....
'....
' nuevo código en un nuevo espacio de nombres
Namespace Personal
Public Class Empleado
Public Function FechaActual() As Date
Dim Fecha As Date
Fecha = Now
Return Fecha
End Function
End Class
End Namespace
Código fuente 251
Tras compilar de nuevo la librería, nos situaremos en el proyecto de consola que accede a ella, y seleccionaremos la opción de menú Ver + Actualizar. Con esta acción, se restablecerán las referencias existentes en este proyecto hacia librerías externas. 
Para poder instanciar ahora un objeto de la clase Empleado que está en la librería, debemos añadir una nueva instrucción de importación en el archivo de código, que importe el espacio de nombres raíz y el nuevo que acabamos de crear. El Código fuente 252 muestra el código actualizado del proyecto ejecutable. En el caso de que no escribiéramos la segunda línea de importación, no podríamos crear objetos de la clase Empleado.

Imports Rutinas ' importar espacios de nombres raíz
Imports Rutinas.Personal ' importar espacios de nombres nuevo
Module Module1
Sub Main()
' instanciar y trabajar con objetos de la librería
Dim oContab As Contabilidad
Dim oEmple As Empleado
oContab = New Contabilidad()
oContab.NombreBalance = "Saldos"
oContab.MostrarBalance()
oEmple = New Empleado()
Console.WriteLine("La fecha es: {0}", oEmple.FechaActual())
Console.Read()
End Sub
End Module
Código fuente 252