Buscador

Interfaces - II

Seguidamente crearemos la clase Empleado. Para que dicha clase pueda implementar (utilizar) las definiciones de un interfaz, emplearemos después de la declaración de la clase, la palabra clave Implements junto al nombre del interfaz que deseamos que implemente la clase. Veamos el Código fuente 289.

Public Class Empleado
Implements Cadena
'....
'....
End Class
Código fuente 289
Esta acción obligará a la clase a crear, como mínimo, tantos miembros como indica el interfaz que implementa, es decir, debemos escribir una propiedad Longitud y un método ObtenerValor( ), o en caso contrario, se producirá un error al intentar ejecutar el programa. Observe el lector, que el editor de código sitúa una marca sobre el nombre del interfaz en la clase mientras que no se hayan implementado todos sus miembros. Ver Figura 122.
Figura 122. Mensaje del interfaz que indica que no se han implementado todos sus miembros.
Para implementar un miembro de un interfaz, en el momento de escribir su declaración, utilizaremos la palabra clave Implements, seguida del interfaz y miembro que implementa. Veamos en el Código fuente 290, la implementación del método ObtenerValor( ) en la clase Empleado, con su código correspodiente.

Public Function ObtenerValor() As String Implements ICadena.ObtenerValor
Return UCase(Me.Nombre)
End Function
Código fuente 290

La necesidad de especificar el interfaz y miembro del mismo que implementa, tiene como ventaja, el que para el nombre del método podemos utilizar uno distinto del que indica el interfaz. Por ejemplo, el anterior método podríamos haberlo escrito como muestra el Código fuente 291.

Public Function DameDatos() As String Implements ICadena.ObtenerValor
Return UCase(Me.Nombre)
End Function
Código fuente 291

Interfaces - I

Un interfaz proporciona, a modo de declaración, una lista de propiedades y métodos, que posteriormente serán codificados en una o varias clases. Debido a su naturaleza declarativa, un interfaz no contiene el código de los miembros que expresa; dicho código será escrito en las clases que implementen el interfaz. 
El concepto de interfaz es análogo al de contrato, las partes integrantes son el propio interfaz y la clase que lo implementa. Mientras que el interfaz no puede ser cambiado desde el momento en que sea implementado, la clase que lo implementa se compromete a crear la lista de miembros en la misma forma que indica el interfaz 
Los interfaces nos permiten definir conjuntos reducidos de funcionalidades, constituyendo una útil herramienta de cara al polimorfismo. El mismo interfaz, implementado en distintas clases, podrá tener a su vez código distinto, con lo que los objetos de diferentes clases que implementen un interfaz común, pueden tener un comportamiento diferente. Supongamos por ejemplo, que necesitamos que algunas clases dispongan de ciertos miembros para la manipulación de cadenas, pero no queremos que estas características sean algo rígido, es decir, cada clase debe de cumplir con un conjunto de nombres y parámetros para los miembros que se encargarán de manipular cadenas, pero la implementación del código que haga cada clase para gestionarlas es libre. 
Ante esta situación podemos definir un interfaz e implementarlo en cada clase.  El ejemplo desarrollado al completo para este caso se encuentra en el proyecto InterfacesPrueba (hacer clic aquí para acceder al ejemplo). 
En primer lugar crearemos un nuevo proyecto de tipo aplicación de consola. A continuación, para crear un interfaz, utilizaremos la palabra clave Interface junto con el nombre que asignemos al interfaz. Para indicar el final del interfaz usaremos la palabra clave End Interface, situando dentro del interfaz las declaraciones de miembros que necesitemos. En nuestro ejemplo vamos a crear el interfaz ICadena que declara la propiedad Longitud y el método ObtenerValor. Aunque no es en absoluto necesario, se recomienda que utilicemos la letra I como prefijo para los nombres de interfaces, de cara a facilitar la lectura del código, como vemos en el Código fuente 288.

' las clases que implementen este interfaz
' deberán crear la propiedad Longitud y el
' método ObtenerValor(); la codificación de
' dichos miembros será particular a cada clase
Public Interface ICadena
ReadOnly Property Longitud() As Integer
Function ObtenerValor() As String
End Interface
Código fuente 288

Definir una clase como punto de entrada de la aplicación

Podemos crear una clase en el proyecto, que contenga un método Main( ) declarado como Shared, de forma que dicho método constituya el punto del programa. Ver Código fuente 287.

Public Class Comienzo
Public Shared Sub Main()
Console.WriteLine("Está comenzando la aplicación")
Console.ReadLine()
End Sub
End Class
Código fuente 287

Además de crear la clase con este método, deberemos modificar las propiedades del proyecto, definiendo como objeto inicial el nombre de la clase o directamente Sub Main. Como habrá podido adivinar el lector, ello hace innecesario el uso de módulos de código dentro del proyecto, pudiendo de esta manera, codificar la aplicación completamente basada en clases. 
Como detalle interesante, debemos destacar el hecho de que al utilizar el modo tradicional de inicio de una aplicación, es decir, a través de un procedimiento Main( ) en un módulo, el compilador convierte internamente dicho módulo en una clase y al procedimiento en un método compartido.

Miembros compartidos (shared) de una clase - II

En el caso de variables de clase declaradas como miembros compartidos, este tipo de variable sólo es creado una vez, manteniendo su valor para todas las instancias de la clase. Esto contrasta con los miembros de instancia, de los que se crea una copia particular para cada objeto. 
El efecto de miembro compartido se hace más patente cuando se aplica sobre variables, por ello, en el ejemplo del Código fuente 286, creamos dos campos compartidos para la clase Empleado; uno de ellos actuará como contador de los objetos creados de la clase, usando el método constructor para ser incrementado. El otro nos servirá para comprobar que siendo compartido no se inicializa, y mantiene el valor asignado previamente.

Module General
Sub Main()
' accedemos a la variable compartida
' y le asignamos valor
Empleado.psApellidos = "Naranjo"
' instanciamos un primer objeto Empleado
Dim loEmp1 As New Empleado()
' asignamos valor a su variable de instancia
loEmp1.psNombre = "Luis"
' mostramos las dos variables del objeto
Console.WriteLine("Objeto loEmp1 - valores de sus variables")
Console.WriteLine("psNombre: {0} - psApellidos: {1}", _
loEmp1.psNombre, loEmp1.psApellidos)
Console.WriteLine()
' instanciamos un segundo objeto Empleado
Dim loEmp2 As New Empleado()
' asignamos valor a su variable de instancia
loEmp2.psNombre = "Juan"
' mostramos las dos variables del objeto
Console.WriteLine("Objeto loEmp2 - valores de sus variables")
Console.WriteLine("psNombre: {0} - psApellidos: {1}", _
loEmp2.psNombre, loEmp2.psApellidos)
Console.WriteLine()
' ahora mostramos el valor de
' la variable compartida miContar
Console.WriteLine("Se han instanciado {0} objetos de la clase Empleado", _
Empleado.piContar)
Console.ReadLine()
End Sub
End Module
Public Class Empleado
Public psNombre As String ' miembro de instancia
Public Shared psApellidos As String ' miembro compartido
Public Shared piContar As Integer ' miembro compartido
Public Sub New()
' por cada instancia de la clase creada,
' incrementar este campo compartido
Me.piContar += 1
End Sub
End Class
Código fuente 286

Miembros compartidos (shared) de una clase - I

Los miembros compartidos o shared son aquellos que no precisan de una instancia previa de un objeto de la clase para poder ser utilizados, aunque pueden también ser usados por una instancia de la clase. Dentro de este contexto, podemos pues clasificar los miembros de una clase en dos categorías: 
  • Miembros de instancia (instance members). Son aquellos a los que accedemos a través de un objeto instanciado previamente de la clase. 
  • Miembros compartidos (shared members). Son aquellos a los que podemos acceder sin necesidad de que exista un objeto creado de la clase. 
Podemos declarar como compartidos los métodos, propiedades y campos de una clase. Para ello deberemos emplear la palabra clave Shared en la declaración. Para utilizar desde el código cliente un miembro compartido, tan sólo debemos poner el nombre de la clase a la que pertenece, el punto y el nombre del miembro a utilizar. El ejemplo del Código fuente 285 demuestra como podemos ejecutar un método compartido sin haber instanciado antes un objeto de la clase a la que pertenece dicho método.

Module General
Sub Main()
Dim lsValor As String
' aunque no hemos instanciado objetos
' de la clase Empleado, podemos llamar
' a este método compartido
Console.WriteLine("Nombre del mes: {0}", Empleado.VerNombreMes())
' ahora creamos una instancia de la clase
Dim loEmpleado1 As New Empleado()
lsValor = loEmpleado1.VerNombreDia()
Console.WriteLine("Nombre del día: {0}", lsValor)
Console.ReadLine()
End Sub
End Module
Public Class Empleado
Public Shared Function VerNombreMes() As String
' este método puede ser llamado
' directamente empleando el nombre
' de la clase como calificador
Return Format(Now(), "MMMM")
End Function
Public Function VerNombreDia() As String
' este método precisa de una instancia
' para ser llamado
Return Format(Now(), "dddd")
End Function
End Class
Código fuente 285

Elementos compartidos e interfaces - III

En el caso de clases heredadas, y con métodos sobrescritos, CType( ) discierne la implementación de clase a la que debe dirigirse. 
Si añadimos la clase Administrativo como hija de Empleado, y sobrescribimos el método VerDatos( ), cuando pasemos al procedimiento ManipularVarios( ) un objeto Administrativo, dado que esta clase hereda de Empleado, con un sencillo cambio en la estructura Select...Case que comprueba el tipo de objeto, ejecutaremos la implementación sobrescrita del método VerDatos( ) en la clase Administrativo. Veamos el Código fuente 283.

Module Module1
Public Sub Main()
Dim loEmple As New Empleado()
loEmple.piID = 58
loEmple.psNombre = "Elena Peral"
ManipularVarios(loEmple)
Dim loAdmin As New Administrativo()
loAdmin.piID = 80
loAdmin.psNombre = "Alfredo Iglesias"
ManipularVarios(loAdmin)
Dim loFac As New Factura()
loFac.pdtFecha = "25/2/2002"
loFac.piImporte = 475
ManipularVarios(loFac)
Console.Read()
End Sub
Public Sub ManipularVarios(ByVal loUnObjeto As Object)
' obtenemos información sobre el tipo del objeto
Dim loTipoObj As Type
loTipoObj = loUnObjeto.GetType()
' comprobamos qué tipo de objeto es,
' y en función de eso, ejecutamos el
' método adecuado
Select Case loTipoObj.Name
Case "Empleado", "Administrativo"
CType(loUnObjeto, Empleado).VerDatos()
Case "Factura"
CType(loUnObjeto, Factura).Emitir()
End Select
End Sub
End Module
Public Class Administrativo
Inherits Empleado
Public pdtFHAlta As Date
Public Overrides Sub VerDatos()
Console.WriteLine("DATOS DEL ADMINISTRATIVO")
Console.WriteLine("==================")
Console.WriteLine("Código: {0}", Me.piID)
Console.WriteLine("Nombre: {0}", Me.psNombre)
End Sub
Public Function MesAlta()
Return Format(Me.pdtFHAlta, "MMMM")
End Function
End Class
Código fuente 283

Pero ¿cómo podríamos ejecutar el método particular MesAlta( ) de Administrativo, que no se encuentra en Empleado?, pues creando en la estructura Select...Case, un caso particular que compruebe cuándo estamos tratando con un objeto Administrativo. Veámoslo en el Código fuente 284.
Public Sub ManipularVarios(ByVal loUnObjeto As Object)
' obtenemos información sobre el tipo del objeto
Dim loTipoObj As Type
loTipoObj = loUnObjeto.GetType()
' comprobamos qué tipo de objeto es,
' y en función de eso, ejecutamos el
' método adecuado
Select Case loTipoObj.Name
Case "Empleado"
CType(loUnObjeto, Empleado).VerDatos()
Case "Administrativo" ' <-- añadimos este caso a la estructura
CType(loUnObjeto, Administrativo).VerDatos()
Console.WriteLine("El administrativo comenzó en {0}", _
CType(loUnObjeto, Administrativo).MesAlta())
Case "Factura"
CType(loUnObjeto, Factura).Emitir()
End Select
End Sub
Código fuente 284

Elementos compartidos e interfaces - II

Conociendo ya el tipo de objeto con el que tratamos, utilizaremos la función CType( ), que realiza un moldeado de la variable que contiene el objeto hacia un tipo determinado, y nos permite acceder a los elementos del objeto. 
El lector puede argumentar que no sería necesario el uso de CType(), y en efecto es así; podríamos haber utilizado directamente la variable, situando a continuación el método a ejecutar. Esta técnica, no obstante, tiene el inconveniente de que utiliza enlace tardío para acceder al objeto. 
CType( ) sin embargo, tiene la ventaja de que opera bajo enlace temprano, con lo cuál, el rendimiento en ejecución es mayor. Veamos el código cliente que accedería a estas clases en el Código fuente 282.

Module Module1
Public Sub Main()
Dim loEmple As New Empleado()
loEmple.piID = 58
loEmple.psNombre = "Elena Peral"
ManipularVarios(loEmple)
Dim loFac As New Factura()
loFac.pdtFecha = "25/2/2002"
loFac.piImporte = 475
ManipularVarios(loFac)
Console.Read()
End Sub
Public Sub ManipularVarios(ByVal loUnObjeto As Object)
' obtenemos información sobre el tipo del objeto
Dim loTipoObj As Type
loTipoObj = loUnObjeto.GetType()
' comprobamos qué tipo de objeto es,
' y en función de eso, ejecutamos el
' método adecuado
Select Case loTipoObj.Name
Case "Empleado"
CType(loUnObjeto, Empleado).VerDatos()
Case "Factura"
CType(loUnObjeto, Factura).Emitir()
End Select
End Sub
End Module
Código fuente 282

Elementos compartidos e interfaces - I

Comprobación del tipo de un objeto y moldeado (casting)

En algunas circunstancias, puede ser útil codificar un procedimiento que reciba como parámetro un objeto genérico, y dependiendo del tipo del objeto, ejecutar ciertos métodos. Si tipificamos un parámetro como un objeto genérico, es decir, de tipo Object, necesitamos implementar dentro del procedimiento un mecanismo que, en primer lugar, compruebe a qué tipo pertenece el objeto, y por otra parte, acceda adecuadamente a su lista de miembros. Como ejemplo consideremos el siguiente caso: creamos una clase Empleado y otra Factura, las cuales vemos en el Código fuente 281.

Public Class Empleado
Public piID As String
Public psNombre As String
Public Overridable Sub VerDatos()
Console.WriteLine("Código de empleado: {0}, nombre: {1}", _
Me.piID, Me.psNombre)
End Sub
End Class
Public Class Factura
Public pdtFecha As Date
Public piImporte As Integer
Public Sub Emitir()
Console.WriteLine("Se procede a generar la siguiente factura:")
Console.WriteLine("Fecha de factura: {0}", Me.pdtFecha)
Console.WriteLine("Importe de la factura: {0}", Me.piImporte)
End Sub
End Class
Código fuente 281
A continuación, necesitamos un procedimiento al que podamos pasar indistintamente un objeto Empleado o Factura. Dentro de dicho procedimiento, al que denominaremos ManipularVarios( ), y que llamaremos desde Main( ), comprobaremos el tipo de objeto llamando al método 

GetType( ), que implementan todos los objetos del entorno, debido a que es un método de la clase Object, y como ya sabemos, todas las clases heredan implícitamente de Object. GetType( ) devuelve un objeto de la clase Type. Esta clase es de una enorme utilidad, ya que nos proporciona toda la información relativa al tipo del objeto. Para nuestro problema particular, interrogaremos a la propiedad Name del objeto, que nos devolverá una cadena con el nombre de la clase a que pertenece el objeto.

Clases abstractas o no instanciables - II

Debemos tener en cuenta que los miembros abstractos sólo tienen sentido si son declarados en clases abstractas. Por tal motivo, sólo podremos crear métodos con MustOverride en clases que hayan sido definidas como MustInherit. 
En lo que respecta al polimorfismo conseguido a través de clases abstractas, podemos crear un procedimiento que reciba como parámetro tipificado como clase abstracta, de forma que en función del objeto pasado, y que debe ser de un tipo derivado de la clase abstracta, el comportamiento será diferente en cada caso. Veámoslo en el Código fuente 280.

Module Module1
Public Sub Main()
'....
End Sub
' el objeto que reciba este procedimiento será
' de cualquiera de las clases que hereden de
' Empleado y ejecutará la implementación del método
' VerDatos() que tenga dicho objeto
Public Sub MostrarInformacion(ByVal loEmple As Empleado)
loEmple.VerDatos()
End Sub
End Module
Código fuente 280

Clases abstractas o no instanciables - I

Una clase abstracta es aquella que no permite la instanciación directa de objetos a partir de ella, siendo necesario una clase derivada para acceder a sus miembros. Una clase concreta es el tipo de clase que venimos utilizando hasta el momento, desde la cuál, podemos instanciar objetos. 
Aunque en una clase abstracta podemos escribir un método constructor, sólo será accesible desde el constructor de la subclase. 
Para definir una clase abstracta utilizaremos la palabra clave MustInherit en el momento de su declaración, como muestra el Código fuente 278.

Public MustInherit Class Empleado
Código fuente 278
Dentro de una clase abstracta podemos implementar propiedades y métodos, en la forma que hemos visto hasta el momento. Adicionalmente, podemos obligar a que determinados miembros sean sobrescritos por la clase heredada; son los denominados miembros abstractos, y se declaran usando la palabra clave MustOverride, como vemos en el Código fuente 279.

Module Module1
Public Sub Main()
Dim loAdmin As New Administrativo()
loAdmin.piID = 789
loAdmin.psNombre = "Pedro Pinares"
Console.WriteLine("Nombre en mayúsculas del administrativo: {0}", _
loAdmin.NombreMay)
loAdmin.pdtFHAlta = "8/10/01"
loAdmin.MesesActivo()
Console.Read()
End Sub
End Module
' clase abstracta,
' no podemos crear objetos Empleado
Public MustInherit Class Empleado
Public piID As Integer
Public psNombre As String
Public pdtFHAlta As Date
Public ReadOnly Property NombreMay() As String
Get
Return UCase(Me.psNombre)
End Get
End Property
' método abstracto;
' este método debe ser sobrescrito
' por la clase derivada
Public MustOverride Sub MesesActivo()
Public Sub VerDatos()
Console.WriteLine("Información sobre el empleado." & _
" ID:{0} - Nombre:{1} - Fecha de alta:{2}", _
Me.piID, Me.psNombre, Me.pdtFHAlta)
Console.WriteLine()
End Sub
End Class
' desde esta clase tendremos acceso
' a los miembros de la clase Empleado
Public Class Administrativo
Inherits Empleado
' en esta clase sobrescribimos este método
' declarado como abstracto en la clase abstracta
Public Overrides Sub MesesActivo()
Console.WriteLine("Entró en el mes de {0}", _
Format(Me.pdtFHAlta, "MMMM"))
Console.WriteLine("El número de meses que lleva es: {0}", _
DateDiff(DateInterval.Month, Me.pdtFHAlta, Now))
End Sub
Public Overrides Sub VerDatos()
'....
'....
End Sub
End Class
Código fuente 279

Clases selladas o no heredables

Toda clase que declaremos en nuestro código es heredable por defecto; esto supone un elevado grado de responsabilidad, en el caso de que diseñemos una clase pensando en que pueda ser utilizada por otros programadores que hereden de ella. Si en un determinado momento, necesitamos hacer cambios en nuestra clase, dichos cambios afectarán a las clases derivadas que hayan sido creadas. 
Por dicho motivo, si no queremos que nuestra clase pueda ser heredada por otras, debemos declararla de forma que no permita herencia; a este tipo de clase se le denomina clase no heredable o sellada (sealed). Para definir una clase no heredable, debemos utilizar la palabra clave NotInheritable en el momento de su declaración. 
En la Figura 121 hemos creado la clase Fichero como no NotInheritable, por ello, cuando a continuación declaramos la clase FiTexto e intentamos que herede de Fichero, se mostrará un mensaje de error en el editor de código, indicándonos que no es posible establecer esta relación de herencia puesto que Fichero es una clase sellada.
Figura 121. No es posible heredar de una clase NotInheritable.

Herencia y métodos constructores - II

Podemos no obstante, evitar la obligación de escribir un constructor en la clase derivada, si en la clase padre creamos un constructor sin parámetros. Como ya sabemos, la sobrecarga de métodos nos permite escribir varios métodos con el mismo nombre y diferente lista de parámetros. Modifiquemos para ello la clase Empleado como muestra el Código fuente 277.

Public Class Empleado
Public piID As Integer
Public psNombre As String
Public piSalario As Integer
' constructor parametrizado
Public Sub New(ByVal lsNombre As String)
Me.psNombre = lsNombre
End Sub
' constructor sin parámetros
Public Sub New()
psNombre = "hola"
End Sub
End Class
Public Class Administrativo
Inherits Empleado
' al disponer en la clase base de
' un constructor normal, ya no hay
' necesidad de crear un constructor
' en esta clase derivada
End Class
Código fuente 277

Finalmente, debemos apuntar dos reglas que debe cumplir todo método constructor de una subclase que llame al constructor de su clase base: en primer lugar, el constructor base debe ser llamado en la primera línea del constructor derivado; en segundo lugar, el constructor base sólo puede ser llamado una vez desde el constructor derivado.

Herencia y métodos constructores - I

Podemos crear una clase base que implemente un constructor y una subclase sin él. En esta situación, cuando instanciemos un objeto de la subclase, se llamará implícitamente al constructor de la clase base para ejecutar el código de inicialización. También es posible crear el constructor sólo en la clase derivada. 
Si ambas clases disponen de un constructor, en primer lugar se ejecutará el constructor de la clase base y después el de la clase derivada. Realmente, el primer constructor ejecutado corresponde a la clase Object, y sucesivamente, se irán ejecutando todos los constructores de la jerarquía de clases hasta llegar a la clase que originó la llamada. El problema sobreviene cuando en la clase base creamos un constructor parametrizado, ya que ello obliga a sus clases derivadas a crear también un método constructor dentro del cuál se haga una llamada al constructor de la clase base. Para llamar explícitamente a un método de la clase base desde una subclase utilizaremos la palabra clave MyBase, que contiene una referencia hacia la clase padre. 
Veamos un ejemplo, en el Código fuente 276 se crea una clase padre Empleado y la subclase Administrativo. Puesto que Empleado dispone de un constructor parametrizado, en Administrativo debemos crear también un constructor, y dentro de él llamar en primer lugar al constructor base.

Public Class Empleado
Public piID As Integer
Public psNombre As String
Public piSalario As Integer
' constructor parametrizado
Public Sub New(ByVal lsNombre As String)
Me.psNombre = lsNombre
End Sub
End Class
Public Class Administrativo
Inherits Empleado
' constructor normal
Public Sub New()
' llamada al constructor
' de la clase base
MyBase.New("Juan")
Me.piSalario = 100
End Sub
End Class
Código fuente 276