3. Clases, estructuras, interfaces
3.1. El objeto a través del ejemplo
3.1.1. Generalidades
Ahora abordaremos, a través de un ejemplo, la programación orientada a objetos. Un objeto es una entidad que contiene datos que definen su estado (denominados «propiedades») y funciones (denominadas «métodos»). Un objeto se crea según un modelo denominado «clase»:
Public Class c1
' atributos
Private p1 As type1
Private p2 As type2
....
' método
Public Sub m1(....)
...
End Sub
' método
Public Function m2(...)
....
End Function
End Class
A partir de la clase C1 anterior, se pueden crear numerosos objetos O1, O2,… Todos tendrán las propiedades p1, p2,… y los métodos m3, m4,… Pero tendrán valores diferentes para sus propiedades pi, por lo que cada uno tendrá un estado propio. Por analogía, la declaración
crea dos objetos (el término es incorrecto aquí) de tipo (clase) Integer. Su única propiedad es su valor. Si O1 es un objeto de tipo C1, O1.p1 designa la propiedad p1 de O1 y O1.m1 el método m1 de O1. Consideremos un primer modelo de objeto: la clase personne.
3.1.2. Definición de la clase persona
La definición de la clase personne será la siguiente:
Public Class personne
' atributos
Private prenom As String
Private nom As String
Private age As Integer
' método
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
' método
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
Aquí tenemos la definición de una clase, es decir, de un tipo de datos. Cuando creemos variables de este tipo, las llamaremos objetos o instancias de clases. Una clase es, por tanto, un molde a partir del cual se construyen los objetos. Los miembros o campos de una clase pueden ser datos (atributos), métodos (funciones) o propiedades. Las propiedades son métodos especiales que sirven para conocer o fijar el valor de los atributos del objeto. Estos campos pueden ir acompañados de una de las tres palabras clave siguientes:
Un campo privado (private) solo es accesible mediante los métodos internos de la clase | |
Un campo público (public) es accesible desde cualquier función, esté definida o no dentro de la clase | |
Un campo protegido (protected) solo es accesible mediante los métodos internos de la clase o de un objeto derivado (véase más adelante el concepto de herencia). |
En general, los datos de una clase se declaran privados, mientras que sus métodos y propiedades se declaran públicos. Esto significa que el usuario de un objeto (el programador):
- no tendrá acceso directo a los datos privados del objeto
- podrá invocar los métodos públicos del objeto y, en particular, aquellos que le den acceso a sus datos privados.
La sintaxis de declaración de una clase es la siguiente:
public class classe
private donnée ou méthode ou propriété privée
public donnée ou méthode ou propriété publique
protected donnée ou méthode ou propriété protégée
end class
El orden de declaración de los atributos private, protected y public es arbitrario.
3.1.3. El método initialize
Volvamos a nuestra clase [personne] declarada como:
Public Class personne
' atributos
Private prenom As String
Private nom As String
Private age As Integer
' método
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
' método
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
¿Cuál es la función del método initialise? Dado que nom, prenom y age son datos privados de la clase personne, las instrucciones:
son ilegales. Debemos inicializar un objeto de tipo personne mediante un método público. Esa es la función del método initialise. Escribiremos:
La escritura p1.initialise es válida porque initialise es de acceso público.
3.1.4. El operador new
La secuencia de instrucciones
es incorrecta. La instrucción
declara p1 como una referencia a un objeto de tipo personne. Este objeto aún no existe y, por lo tanto, p1 no está inicializado. Es como si se escribiera:
donde se indica explícitamente con la palabra clave nothing que la variable p1 aún no hace referencia a ningún objeto. Cuando luego se escribe
se invoca el método initialise del objeto al que hace referencia p1. Sin embargo, este objeto aún no existe y el compilador señalará el error. Para que p1 haga referencia a un objeto, hay que escribir:
Esto tiene como efecto la creación de un objeto de tipo personne aún sin inicializar: los atributos nom y prenom, que son referencias a objetos de tipo String, tendrán el valor nothing, y age tendrá el valor 0. Por lo tanto, hay una inicialización por defecto. Ahora que p1 hace referencia a un objeto, la instrucción de inicialización de este objeto
es válida.
3.1.5. La palabra clave Me
Veamos el código del método initialise:
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
La instrucción Me.prenom=P significa que el atributo prenom del objeto actual (Me) recibe el valor P. La palabra clave Me designa el objeto actual: aquel en el que se encuentra el método ejecutado. ¿Cómo lo sabemos? Veamos cómo se inicializa el objeto al que hace referencia p1 en el programa llamante:
Se llama al método initialise del objeto p1. Cuando en este método se hace referencia al objeto Me, en realidad se hace referencia al objeto p1. El método initialise también se podría haber escrito de la siguiente manera:
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
prenom = P
nom = N
Me.age = age
End Sub
Cuando un método de un objeto hace referencia a un atributo A de dicho objeto, la escritura Me.A es implícita. Debe utilizarse explícitamente cuando existe un conflicto de identificadores. Este es el caso de la instrucción:
Me.age=age;
donde age hace referencia a un atributo del objeto actual, así como al parámetro age recibido por el método. Por lo tanto, hay que resolver la ambigüedad designando el atributo age como Me.age.
3.1.6. Un programa de prueba
A continuación se muestra un breve programa de prueba:
Public Class personne
' atributos
Private prenom As String
Private nom As String
Private age As Integer
' método
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
' método
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
y los resultados obtenidos:
dos>vbc personne1.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
3.1.7. Utilizar un archivo de clases compiladas (assembly)
Cabe señalar que en el ejemplo anterior hay dos clases en nuestro programa de prueba: las clases personne y test1. Hay otra forma de proceder:
-
se compila la clase persona en un archivo específico denominado ensamblado (assembly). Este archivo tiene la extensión .dll
-
se compila la clase test1 haciendo referencia al ensamblado que contiene la clase persona.
Los dos archivos fuente quedan así:
test.vb | |
personne2.vb | |
La clase personne se compila mediante la siguiente instrucción:
dos>vbc /t:library personne2.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
24/02/2004 16:50 509 personne2.vb
24/02/2004 16:49 143 test.vb
24/02/2004 16:50 3 584 personne2.dll
La compilación ha generado un archivo personne2.dll. Es la opción de compilación /t:library la que indica que se genere un archivo «assembly». Ahora compilemos el archivo test.vb:
dos>vbc /r:personne2.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
24/02/2004 16:50 509 personne2.vb
24/02/2004 16:49 143 test.vb
24/02/2004 16:50 3 584 personne2.dll
24/02/2004 16:51 3 072 test.exe
La opción de compilación /r:personne2.dll indica al compilador que encontrará ciertas clases en el archivo personne2.dll. Cuando en el archivo fuente test.vb encuentre una referencia a la clase personne, clase no declarada en el archivo fuente test.vb, buscará la clase personne en los archivos .dll referenciados por la opción /r. Aquí encontrará la clase personne en el ensamblado personne2.dll. Se podrían haber incluido otras clases en este ensamblado. Para utilizar varios archivos de clases compiladas durante la compilación, se escribirá:
La ejecución del programa test1.exe da los siguientes resultados:
3.1.8. Otro método inicializa
Consideremos de nuevo la clase personne y añadamos el siguiente método:
' método
Public Sub initialise(ByVal P As personne)
prenom = P.prenom
nom = P.nom
Me.age = P.age
End Sub
Ahora tenemos dos métodos con el nombre initialise: esto es válido siempre que admitan parámetros diferentes. Este es el caso aquí. El parámetro es ahora una referencia P a una persona. Los atributos de la persona P se asignan entonces al objeto actual (Me). Cabe señalar que el método initialise tiene acceso directo a los atributos del objeto P, aunque estos sean de tipo private. Esto siempre es así: un objeto O1 de una clase C siempre tiene acceso a los atributos de los objetos de la misma clase C. A continuación se muestra una prueba de la nueva clase personne, que se ha compilado en personne.dll tal y como se ha explicado anteriormente:
' opciones
Option Explicit On
Option Strict On
' espacios de nombres
Imports System
Module test1
Sub Main()
Dim p1 As New personne
p1.initialise("Jean", "Dupont", 30)
Console.Out.Write("p1=")
p1.identifie()
Dim p2 As New personne
p2.initialise(p1)
Console.Out.Write("p2=")
p2.identifie()
End Sub
End Module
y sus resultados:
3.1.9. Constructores de la clase persona
Un constructor es un procedimiento que lleva el nombre New y que se invoca al crear el objeto. Se suele utilizar para inicializarlo. Si una clase tiene un constructor que admite n argumentos argi, la declaración e inicialización de un objeto de esta clase se puede realizar de la forma:
dim objet as classe =new classe(arg1,arg2, ... argn)
o
dim objet as classe
…
objet=new classe(arg1,arg2, ... argn)
Cuando una clase tiene uno o varios constructores, es obligatorio utilizar uno de ellos para crear un objeto de dicha clase. Si una clase C no tiene ningún constructor, tiene uno por defecto, que es el constructor sin parámetros: public New(). Los atributos del objeto se inicializan entonces con valores por defecto. Esto es lo que ocurrió en los programas anteriores, donde habíamos escrito:
Creemos dos constructores para nuestra clase personne:
' opciones
Option Explicit On
Option Strict On
' espacios de nombres
Imports System
' la clase persona
Public Class personne
' atributos
Private prenom As String
Private nom As String
Private age As Integer
' constructores
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
initialise(P, N, age)
End Sub
Public Sub New(ByVal P As personne)
initialise(P)
End Sub
' métodos de inicialización del objeto
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
Public Sub initialise(ByVal P As personne)
prenom = P.prenom
nom = P.nom
Me.age = P.age
End Sub
' método
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
Nuestros dos constructores se limitan a llamar a los métodos initialise correspondientes. Recordemos que cuando en un constructor encontramos la notación initialise(P), por ejemplo, el compilador la traduce como Me.initialise(P). En el constructor, se invoca el método initialise para trabajar sobre el objeto al que hace referencia Me, es decir, el objeto actual, el que se está construyendo. He aquí un breve programa de prueba:
' opciones
Option Explicit On
Option Strict On
' espacios de nombres
Imports System
' página de prueba
Module test
Sub Main()
Dim p1 As New personne("Jean", "Dupont", 30)
Console.Out.Write("p1=")
p1.identifie()
Dim p2 As New personne(p1)
Console.Out.Write("p2=")
p2.identifie()
End Sub
End Module
y los resultados obtenidos:
3.1.10. Las referencias de objetos
Seguimos utilizando la misma clase personne. El programa de prueba queda así:
' opciones
Option Explicit On
Option Strict On
' espacios de nombres
Imports System
' páginas de prueba
Module test
Sub Main()
' p1
Dim p1 As New personne("Jean", "Dupont", 30)
Console.Out.Write("p1=")
p1.identifie()
' p2 hace referencia al mismo objeto que p1
Dim p2 As personne = p1
Console.Out.Write("p2=")
p2.identifie()
' p3 hace referencia a un objeto que será una copia del objeto al que hace referencia p1
Dim p3 As New personne(p1)
Console.Out.Write("p3=")
p3.identifie()
' se cambia el estado del objeto al que hace referencia p1
p1.initialise("Micheline", "Benoît", 67)
Console.Out.Write("p1=")
p1.identifie()
' como p2 = p1, el objeto al que hace referencia p2 debe haber cambiado de estado
Console.Out.Write("p2=")
p2.identifie()
' como p3 no hace referencia al mismo objeto que p1, el objeto al que hace referencia p3 no debe de haber cambiado
Console.Out.Write("p3=")
p3.identifie()
End Sub
End Module
Los resultados obtenidos son los siguientes:
p1=Jean,Dupont,30
p2=Jean,Dupont,30
p3=Jean,Dupont,30
p1=Micheline,Benoît,67
p2=Micheline,Benoît,67
p3=Jean,Dupont,30
Al declarar la variable p1 mediante
p1 hace referencia al objeto personne("Jean","Dupont",30), pero no es el objeto en sí. En C, se diría que es un puntero, c.a.d, a la dirección del objeto creado. Si a continuación escribimos:
No es el objeto personne("Jean","Dupont",30) el que se modifica, sino que es la referencia p1 la que cambia de valor. El objeto personne("Jean","Dupont",30) se «perderá» si no lo referencia ninguna otra variable.
Cuando se escribe:
se inicializa el puntero p2: «apunta» al mismo objeto (designa el mismo objeto) que el puntero p1. Así, si se modifica el objeto «apuntado» (o referenciado) por p1, se modifica el referenciado por p2.
Cuando se escribe:
se crea un nuevo objeto, copia del objeto al que hace referencia p1. Este nuevo objeto será referenciado por p3. Si se modifica el objeto «apuntado» (o referenciado) por p1, no se modifica en absoluto el referenciado por p3. Esto es lo que muestran los resultados obtenidos.
3.1.11. Los objetos temporales
En una expresión, se puede invocar explícitamente al constructor de un objeto: este se construye, pero no tenemos acceso a él (para modificarlo, por ejemplo). Este objeto temporal se construye para evaluar la expresión y luego se descarta. El espacio de memoria que ocupaba será recuperado automáticamente más tarde por un programa denominado «reciclador de basura», cuya función es recuperar el espacio de memoria ocupado por objetos a los que ya no hacen referencia los datos del programa. Consideremos el siguiente nuevo programa de prueba:
' opciones
Option Strict On
Option Explicit On
' espacios de nombres
Imports System
' página de prueba
Module test
Sub Main()
Dim p As New personne(New personne("Jean", "Dupont", 30))
p.identifie()
End Sub
End Module
y modifiquemos los constructores de la clase personne para que muestren un mensaje:
' constructores
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
Console.Out.WriteLine("Constructeur personne(String, String, integer)")
initialise(P, N, age)
End Sub
Public Sub New(ByVal P As personne)
Console.Out.WriteLine("Constructeur personne(personne)")
initialise(P)
End Sub
Obtenemos los siguientes resultados:
dos>test
Constructeur personne(String, String, integer)
Constructeur personne(personne)
Jean,Dupont,30
lo que muestra la construcción sucesiva de los dos objetos temporales.
3.1.12. Métodos de lectura y escritura de los atributos privados
Añadimos a la clase personne los métodos necesarios para leer o modificar el estado de los atributos de los objetos:
Imports System
Public Class personne
' atributos
Private prenom As [String]
Private nom As [String]
Private age As Integer
' constructores
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
Public Sub New(ByVal P As personne)
Me.prenom = P.prenom
Me.nom = P.nom
Me.age = P.age
End Sub
' identificadores
Public Sub identifie()
Console.Out.WriteLine((prenom + "," + nom + "," + age))
End Sub
' accesores
Public Function getPrenom() As [String]
Return prenom
End Function
Public Function getNom() As [String]
Return nom
End Function
Public Function getAge() As Integer
Return age
End Function
'modificadores
Public Sub setPrenom(ByVal P As [String])
Me.prenom = P
End Sub
Public Sub setNom(ByVal N As [String])
Me.nom = N
End Sub
Public Sub setAge(ByVal age As Integer)
Me.age = age
End Sub
End Class
Probamos la nueva clase con el siguiente programa:
' opciones
Option Strict On
Option Explicit On
' espacios de nombres
Imports System
' páginas de prueba
Public Module test
Sub Main()
Dim P As New personne("Jean", "Michelin", 34)
Console.Out.WriteLine(("P=(" & P.getPrenom() & "," & P.getNom() & "," & P.getAge() & ")"))
P.setAge(56)
Console.Out.WriteLine(("P=(" & P.getPrenom() & "," & P.getNom() & "," & P.getAge() & ")"))
End Sub
End Module
y obtenemos los siguientes resultados:
3.1.13. Las propiedades
Existe otra forma de acceder a los atributos de una clase: creando propiedades. Estas nos permiten manipular atributos privados como si fueran públicos. Consideremos la siguiente clase personne, en la que los accesores y modificadores anteriores han sido sustituidos por propiedades de lectura y escritura:
' opciones
Option Explicit On
Option Strict On
' espacios de nombres
Imports System
' clase persona
Public Class personne
' atributos
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' constructores
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
Me._prenom = P
Me._nom = N
Me._age = age
End Sub
Public Sub New(ByVal P As personne)
Me._prenom = P._prenom
Me._nom = P._nom
Me._age = P._age
End Sub
' identificadores
Public Sub identifie()
Console.Out.WriteLine((_prenom & "," & _nom & "," & _age))
End Sub
' propiedades
Public Property prenom() As String
Get
Return _prenom
End Get
Set(ByVal Value As String)
_prenom = Value
End Set
End Property
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
_nom = Value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
' ¿edad válida?
If Value >= 0 Then
_age = Value
Else
Throw New Exception("âge (" & Value & ") invalide")
End If
End Set
End Property
End Class
Una propiedad permite leer (get) o establecer (set) el valor de un atributo. En nuestro ejemplo, hemos antepuesto el carácter _ a los nombres de los atributos para que las propiedades tengan el mismo nombre que los atributos primitivos. De hecho, una propiedad no puede tener el mismo nombre que el atributo que gestiona, ya que entonces se produciría un conflicto de nombres en la clase. Por lo tanto, hemos llamado a nuestros atributos _prenom, _nom, _age y hemos modificado los constructores y métodos en consecuencia. A continuación, hemos creado tres propiedades: nom, prenom y age. Una propiedad se declara de la siguiente manera:
Public Property nom() As Type
Get
...
End Get
Set(ByVal Value As Type)
...
End Set
End Property
donde Type debe ser el tipo del atributo gestionado por la propiedad. Puede tener dos métodos llamados get y set. El método get suele encargarse de devolver el valor del atributo que gestiona (podría devolver otra cosa, nada se lo impide). El método set recibe un parámetro llamado value que normalmente asigna al atributo que gestiona. Puede aprovechar para comprobar la validez del valor recibido y, en su caso, lanzar una excepción si el valor resulta inválido. Esto es lo que se hace aquí con la edad.
¿Cómo se invocan estos métodos get y set? Consideremos el siguiente programa de prueba:
' opciones
Option Explicit On
Option Strict On
' espacios de nombres
Imports System
' página de prueba
Module test
Sub Main()
Dim P As New personne("Jean", "Michelin", 34)
Console.Out.WriteLine(("P=(" & P.prenom & "," & P.nom & "," & P.age & ")"))
P.age = 56
Console.Out.WriteLine(("P=(" & P.prenom & "," & P.nom & "," & P.age & ")"))
Try
P.age = -4
Catch ex As Exception
Console.Error.WriteLine(ex.Message)
End Try
End Sub
End Module
En la instrucción
se busca obtener los valores de las propiedades prenom, nom y age de la persona P. Es el método get de estas propiedades el que se invoca y el que devuelve el valor del atributo que gestionan.
En la instrucción
se quiere establecer el valor de la propiedad age. En ese caso, se invoca el método set de dicha propiedad. Recibirá 56 en su parámetro value.
Una propiedad P de una clase C que solo definiera el método get se denomina de solo lectura. Si c es un objeto de la clase C, el compilador rechazará la operación c.P=valor.
La ejecución del programa de prueba anterior da los siguientes resultados:
Las propiedades nos permiten, por tanto, manipular atributos privados como si fueran públicos.
3.1.14. Métodos y atributos de clase
Supongamos que queremos contar el número de objetos [personne] creados en una aplicación. Podemos gestionar nosotros mismos un contador, pero corremos el riesgo de olvidarnos de los objetos temporales que se crean aquí y allá. Parecería más seguro incluir en los constructores de la clase [personne] una instrucción que incremente un contador. El problema es pasar una referencia de este contador para que el constructor pueda incrementarlo: hay que pasarles un nuevo parámetro. También se puede incluir el contador en la definición de la clase. Como se trata de un atributo de la propia clase y no de un objeto concreto de dicha clase, se declara de forma diferente con la palabra clave Shared:
Para hacer referencia a él, se escribe personne._nbPersonnes para indicar que es un atributo de la propia clase personne. Aquí hemos creado un atributo privado al que no se tendrá acceso directo fuera de la clase. Por lo tanto, creamos una propiedad pública para dar acceso al atributo de clase nbPersonnes. Para establecer el valor de nbPersonnes, el método get de esta propiedad no necesita un objeto personne concreto: de hecho, _nbPersonnes no es el atributo de un objeto concreto, sino el atributo de toda una clase. Por lo tanto, necesitamos una propiedad declarada también como Shared:
que desde el exterior se llamará con la sintaxis personne.nbPersonnes. La propiedad se declara de solo lectura (ReadOnly) ya que no ofrece ningún método set. A continuación se muestra un ejemplo. La clase personne queda de la siguiente manera:
Option Explicit On
Option Strict On
' espacios de nombres
Imports System
' clase
Public Class personne
' atributos de clase
Private Shared _nbPersonnes As Long = 0
' atributos de instancia
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' constructores
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
' una persona más
_nbPersonnes += 1
Me._prenom = P
Me._nom = N
Me._age = age
End Sub
Public Sub New(ByVal P As personne)
' una persona más
_nbPersonnes += 1
Me._prenom = P._prenom
Me._nom = P._nom
Me._age = P._age
End Sub
' identifica
Public Sub identifie()
Console.Out.WriteLine((_prenom & "," & _nom & "," & _age))
End Sub
' propiedad de clase
Public Shared ReadOnly Property nbPersonnes() As Long
Get
Return _nbPersonnes
End Get
End Property
' propiedades de instancia
Public Property prenom() As String
Get
Return _prenom
End Get
Set(ByVal Value As String)
_prenom = Value
End Set
End Property
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
_nom = Value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
' ¿edad válida?
If Value >= 0 Then
_age = Value
Else
Throw New Exception("âge (" & Value & ") invalide")
End If
End Set
End Property
End Class
Con el siguiente programa:
' opciones
Option Explicit On
Option Strict On
' espacios de nombres
Imports System
' página de prueba
Module test
Sub Main()
Dim p1 As New personne("Jean", "Dupont", 30)
Dim p2 As New personne(p1)
Console.Out.WriteLine(("Nombre de personnes créées : " & personne.nbPersonnes))
End Sub
End Module
se obtienen los siguientes resultados:
3.1.15. Pasar un objeto a una función
Ya hemos dicho que, por defecto, VB.NET pasa los parámetros efectivos de una función por valor: los valores de los parámetros efectivos se copian en los parámetros formales. En el caso de un objeto, no hay que dejarse engañar por el uso incorrecto del lenguaje que se hace sistemáticamente al hablar de objeto en lugar de referencia de objeto. Un objeto solo se manipula a través de una referencia (un puntero) sobre él. Por lo tanto, lo que se transmite a una función no es el objeto en sí, sino una referencia a dicho objeto. Así pues, es el valor de la referencia y no el valor del objeto en sí lo que se duplica en el parámetro formal: no se crea un nuevo objeto. Si se transmite una referencia de objeto R1 a una función, se copiará en el parámetro formal correspondiente R2. Por lo tanto, las referencias R2 y R1 designan al mismo objeto. Si la función modifica el objeto al que apunta R2, evidentemente modifica también el objeto al que hace referencia R1, ya que se trata del mismo.
![]() |
Esto es lo que muestra el siguiente ejemplo:
' opciones
Option Explicit On
Option Strict On
' espacios de nombres
Imports System
' páginas de prueba
Module test
Sub Main()
' una persona p1
Dim p1 As New personne("Jean", "Dupont", 30)
' visualización p1
Console.Out.Write("Paramètre effectif avant modification : ")
p1.identifie()
' modificación p1
modifie(p1)
' visualización p1
Console.Out.Write("Paramètre effectif après modification : ")
p1.identifie()
End Sub
Sub modifie(ByVal P As personne)
' visualización de persona P
Console.Out.Write("Paramètre formel avant modification : ")
P.identifie()
' modificación P
P.prenom = "Sylvie"
P.nom = "Vartan"
P.age = 52
' visualización P
Console.Out.Write("Paramètre formel après modification : ")
P.identifie()
End Sub
End Module
Los resultados obtenidos son los siguientes:
Paramètre effectif avant modification : Jean,Dupont,30
Paramètre formel avant modification : Jean,Dupont,30
Paramètre formel après modification : Sylvie,Vartan,52
Paramètre effectif après modification : Sylvie,Vartan,52
Se observa que solo se ha creado un objeto: el de la persona p1 del procedimiento Main, y que el objeto ha sido modificado correctamente por la función modifie.
3.1.16. Una tabla de personas
Un objeto es un dato como cualquier otro y, como tal, se pueden agrupar varios objetos en una tabla:
' opciones
Option Explicit On
Option Strict On
' espacios de nombres
Imports System
' página de prueba
Module test
Sub Main()
' una tabla de personas
Dim amis(2) As personne
amis(0) = New personne("Jean", "Dupont", 30)
amis(1) = New personne("Sylvie", "Vartan", 52)
amis(2) = New personne("Neil", "Armstrong", 66)
' visualización
Console.Out.WriteLine("----------------")
Dim i As Integer
For i = 0 To amis.Length - 1
amis(i).identifie()
Next i
End Sub
End Module
La instrucción Dim amigos(2) As persona crea una matriz de 3 elementos de tipo personne. Estos 3 elementos se inicializan aquí con los valores nothing y c.a.d, ya que no hacen referencia a ningún objeto. De nuevo, por abuso de lenguaje, hablamos de matriz de objetos cuando en realidad se trata solo de una matriz de referencias a objetos. La creación de la matriz de objetos, que es en sí misma un objeto, no crea ningún objeto del tipo de sus elementos: hay que hacerlo después. Se obtienen los siguientes resultados:
3.2. La herencia a través del ejemplo
3.2.1. Generalidades
Aquí abordamos el concepto de herencia. El objetivo de la herencia es «personalizar» una clase existente para que satisfaga nuestras necesidades. Supongamos que queremos crear una clase enseignant: un profesor es una persona concreta. Tiene atributos que otra persona no tendrá: la asignatura que imparte, por ejemplo. Pero también tiene los atributos de cualquier persona: nombre, apellidos y edad. Por lo tanto, un profesor forma parte plenamente de la clase personne, pero tiene atributos adicionales. En lugar de escribir una clase enseignant desde cero, preferiríamos aprovechar lo ya creado de la clase personne y adaptarlo al carácter particular de los profesores. Es el concepto de herencia el que nos permite hacer esto. Para expresar que la clase enseignant hereda las propiedades de la clase personne, escribiremos:
Public Class enseignant
Inherits personne
Cabe destacar la sintaxis particular en dos líneas. La clase personne se denomina clase padre (o madre) y la clase enseignant, clase derivada (o hija). Un objeto enseignant tiene todas las características de un objeto personne: tiene los mismos atributos y los mismos métodos. Estos atributos y métodos de la clase padre no se repiten en la definición de la clase hija: basta con indicar los atributos y métodos añadidos por la clase hija. Suponemos que la clase personne se define de la siguiente manera:
' opciones
Option Strict On
Option Explicit On
' espacios de nombres
Imports System
' clase persona
Public Class personne
' atributos de clase
Private Shared _nbPersonnes As Long = 0
' atributos de instancia
Private _prenom As [String]
Private _nom As [String]
Private _age As Integer
' constructores
Public Sub New(ByVal P As [String], ByVal N As [String], ByVal age As Integer)
' una persona más
_nbPersonnes += 1
' construcción
Me._prenom = P
Me._nom = N
Me._age = age
' seguimiento
Console.Out.WriteLine("Construction personne(string, string, int)")
End Sub
Public Sub New(ByVal P As personne)
' una persona más
_nbPersonnes += 1
' construcción
Me._prenom = P._prenom
Me._nom = P._nom
Me._age = P._age
' seguimiento
Console.Out.WriteLine("Construction personne(string, string, int)")
End Sub
' propiedad de clase
Public Shared ReadOnly Property nbPersonnes() As Long
Get
Return _nbPersonnes
End Get
End Property
' propiedades de instancia
Public Property prenom() As String
Get
Return _prenom
End Get
Set(ByVal Value As String)
_prenom = Value
End Set
End Property
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
_nom = Value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
' ¿edad válida?
If Value >= 0 Then
_age = Value
Else
Throw New Exception("âge (" & Value & ") invalide")
End If
End Set
End Property
Public ReadOnly Property identite() As String
Get
Return "personne(" & _prenom & "," & _nom & "," & age & ")"
End Get
End Property
End Class
El método identifie ha sido sustituido por la propiedad identité, de solo lectura, que identifica a la persona. Creamos una clase enseignant que hereda de la clase personne:
' opciones
Option Strict On
Option Explicit On
' espacios de nombres
Imports System
Public Class enseignant
Inherits personne
' atributos
Private _section As Integer
' constructor
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
MyBase.New(P, N, age)
Me._section = section
' seguimiento
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
' propiedad de sección
Public Property section() As Integer
Get
Return _section
End Get
Set(ByVal Value As Integer)
_section = Value
End Set
End Property
End Class
La clase enseignant añade a los métodos y atributos de la clase personne:
- un atributo section, que es el número de sección a la que pertenece el profesor dentro del cuerpo docente (una sección por disciplina, en términos generales)
- un nuevo constructor que permite inicializar todos los atributos de un profesor
La declaración
Public Class enseignant
Inherits personne
indica que la clase enseignant deriva de la clase personne.
3.2.2. Creación de un objeto profesor
El constructor de la clase enseignant es el siguiente:
' constructor
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
MyBase.New(P, N, age)
Me._section = section
' seguimiento
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
La declaración
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
indica que el constructor recibe cuatro parámetros: P, N, age y section. Debe pasar tres de ellos, (P,N,age), a su clase base, en este caso la clase personne. Sabemos que esta clase tiene un constructor persona(string, string, int) que permitirá construir una persona con los parámetros pasados (P,N,age). La clase [enseignant] pasa los parámetros (P, N, edad) a su clase base de la siguiente manera:
MyBase.New(P, N, age)
Una vez finalizada la construcción de la clase base, la construcción del objeto enseignant continúa con la ejecución del cuerpo del constructor:
Me._section = section
En resumen, el constructor de una clase derivada:
- pasa a su clase base los parámetros que esta necesita para construirse
- utiliza los demás parámetros para inicializar los atributos que le son propios
Se podría haber optado por escribir:
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
Me._prenom = P
Me._nom = N
Me._age = age
Me._section = section
' seguimiento
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
Es imposible. La clase personne ha declarado privados (private) sus tres campos _prenom, _nom y _age. Solo los objetos de la misma clase tienen acceso directo a estos campos. Todos los demás objetos, incluidos los objetos derivados como en este caso, deben pasar por métodos públicos para acceder a ellos. Esto habría sido diferente si la clase personne hubiera declarado protegidos (protected) los tres campos: en ese caso, habría permitido a las clases derivadas tener acceso directo a los tres campos. En nuestro ejemplo, utilizar el constructor de la clase padre era, por tanto, la solución correcta y es el método habitual: al construir un objeto hijo, primero se llama al constructor del objeto padre y luego se completan las inicializaciones propias del objeto hijo (section en nuestro ejemplo).
Compilemos las clases personne y enseignant en ensamblados:
dos>vbc /t:library personne.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>vbc /r:personne.dll /t:library enseignant.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
25/02/2004 10:08 1 828 personne.vb
25/02/2004 10:11 675 enseignant.vb
25/02/2004 10:12 223 test.vb
25/02/2004 10:16 4 096 personne.dll
25/02/2004 10:16 3 584 enseignant.dll
Cabe destacar que, para compilar la clase hija enseignant, ha sido necesario hacer referencia al archivo personne.dll, que contiene la clase personne. Probemos con un primer programa de prueba:
' opciones
Option Strict On
Option Explicit On
' espacios de nombres
Imports System
Module test
Sub Main()
Console.Out.WriteLine(New enseignant("Jean", "Dupont", 30, 27).identite)
End Sub
End Module
Este programa se limita a crear un objeto enseignant (new) e identificarlo. La clase enseignant no tiene el método identité, pero su clase padre sí lo tiene y, además, es público: por herencia, se convierte en un método público de la clase enseignant. Los resultados obtenidos son los siguientes:
dos>vbc /r:personne.dll /r:enseignant.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>test
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
personne(Jean,Dupont,30)
Se observa que:
- se ha creado un objeto personne antes que el objeto enseignant
- la identidad obtenida es la del objeto personne
3.2.3. Sobrecarga de un método o una propiedad
En el ejemplo anterior, obtuvimos la identidad de la parte personne del profesor, pero falta cierta información específica de la clase enseignant (la sección). Por lo tanto, debemos escribir una propiedad que permita identificar al profesor:
' opciones
Option Strict On
Option Explicit On
' espacios de nombres
Imports System
Public Class enseignant
Inherits personne
' atributos
Private _section As Integer
' constructor
Public Sub New(ByVal P As String, ByVal N As String, ByVal age As Integer, ByVal section As Integer)
MyBase.New(P, N, age)
Me._section = section
' seguimiento
Console.Out.WriteLine("Construction enseignant(string,string,int,int)")
End Sub
' propiedad de sección
Public Property section() As Integer
Get
Return _section
End Get
Set(ByVal Value As Integer)
_section = Value
End Set
End Property
' sobrecarga de la propiedad identidad
Public Shadows ReadOnly Property identite() As String
Get
Return "enseignant(" & MyBase.identite & "," & _section & ")"
End Get
End Property
End Class
El método identite de la clase enseignant se basa en el método identite de su clase padre (MyBase.identite) para mostrar su parte «personne» y, a continuación, se completa con el campo _section, que es propio de la clase enseignant. Cabe destacar la declaración de la propiedad identite:
Public Shadows ReadOnly Property identite() As String
que indica que la propiedad identite «oculta» el método del mismo nombre que podría existir en la clase padre. Sea un objeto enseignant E. Este objeto contiene en su interior un objeto personne:
![]() |
La propiedad «identity» se define tanto en la clase enseignant como en su clase padre personne. En la clase hija enseignant, la propiedad identite debe ir precedida de la palabra clave shadows para indicar que se redefine una nueva propiedad identite para la clase enseignant.
Public Shadows ReadOnly Property identite() As String
La clase enseignant dispone ahora de dos propiedades identite:
- la heredada de la clase padre personne
- la propia
Si E es un objeto enseignant, E.identite designa el método identite de la clase enseignant. Se dice que la propiedad identite de la clase madre está «redefinida» por la propiedad identite de la clase hija. En general, si O es un objeto y M un método, para ejecutar el método O.M, el sistema busca un método M en el siguiente orden:
- en la clase del objeto O
- en su clase padre, si la tiene
- en la clase madre de su clase madre, si existe
- etc…
La herencia permite, por tanto, redefinir en la clase hija métodos o propiedades con el mismo nombre que en la clase madre. Esto es lo que permite adaptar la clase hija a sus propias necesidades. Junto con el polimorfismo, que veremos más adelante, la sobrecarga de métodos o propiedades es el principal interés de la herencia. Consideremos el mismo ejemplo que antes:
' opciones
Option Strict On
Option Explicit On
' espacios de nombres
Imports System
Module test
Sub Main()
Console.Out.WriteLine(New enseignant("Jean", "Dupont", 30, 27).identite)
End Sub
End Module
Los resultados obtenidos esta vez son los siguientes:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant(personne(Jean,Dupont,30),27)
3.2.4. El polimorfismo
Consideremos una línea de clases: C0 C1 C2 … Cn donde Ci Cj indica que la clase Cj deriva de la clase Ci. Esto implica que la clase Cj tiene todas las características de la clase Ci más otras adicionales. Sean Oi objetos de tipo Ci. Es válido escribir:
De hecho, por herencia, la clase Cj tiene todas las características de la clase Ci más otras. Por lo tanto, un objeto Oj de tipo Cj contiene en sí mismo un objeto de tipo Ci. La operación
hace que Oi sea una referencia al objeto de tipo Ci contenido en el objeto Oj.
El hecho de que una variable Oi de la clase Ci pueda, de hecho, hacer referencia no solo a un objeto de la clase Ci, sino a cualquier objeto derivado de la clase Ci, se denomina polimorfismo: la capacidad de una variable para hacer referencia a diferentes tipos de objetos. Tomemos un ejemplo y consideremos la siguiente función independiente de cualquier clase:
Sub affiche(ByVal p As personne)
También podríamos escribir
que
En este último caso, el parámetro formal de tipo personne de la función affiche recibirá un valor de tipo enseignant. Dado que el tipo enseignant deriva del tipo personne, esto es válido.
3.2.5. Redefinición y polimorfismo
Completemos nuestro procedimiento affiche:
Sub affiche(ByVal p As personne)
' muestra identidad de p
Console.Out.WriteLine(p.identite)
End Sub
El método p.identite devuelve una cadena de caracteres que identifica el objeto personne. ¿Qué ocurre en el caso de nuestro ejemplo anterior con un objeto enseignant?
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
Veamos el siguiente ejemplo:
' opciones
Option Strict On
Option Explicit On
' espacios de nombres
Imports System
Module test
Sub Main()
' un profesor
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' una persona
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' muestra
Sub affiche(ByVal p As personne)
' muestra identidad de p
Console.Out.WriteLine(p.identite)
End Sub
End Module
Los resultados obtenidos son los siguientes:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
personne(Lucile,Dumas,56)
Construction personne(string, string, int)
personne(Jean,Dupont,30)
La ejecución muestra que la instrucción p.identite ejecutó cada vez la propiedad identite de una personne, la persona contenida en la enseignant e, y luego la propia personne p. No se ha adaptado al objeto realmente pasado como parámetro a affiche. Hubiéramos preferido tener la identidad completa del enseignant e. Para ello, habría sido necesario que la notación p.identite hiciera referencia a la propiedad identite del objeto al que realmente apunta p, en lugar de a la propiedad identite de la parte «personne» del objeto al que apunta realmente p. Es posible obtener este resultado declarando identite como una propiedad redefinible (overridable) en la clase base personne:
Public Overridable ReadOnly Property identite() As String
Get
Return "personne(" & _prenom & "," & _nom & "," & age & ")"
End Get
End Property
La palabra clave overridable convierte a identite en una propiedad redefinible o virtual. Esta palabra clave también se puede aplicar a los métodos. Las clases derivadas que redefinen una propiedad o método virtual deben utilizar entonces la palabra clave overrides en lugar de shadows para calificar su propiedad/método redefinido. Así, en la clase enseignant, la propiedad identite se define de la siguiente manera:
' sobrescrito propiedad identidad
Public Overrides ReadOnly Property identite() As String
Get
Return "enseignant(" & MyBase.identite & "," & _section & ")"
End Get
End Property
El programa de prueba:
' opciones
Option Strict On
Option Explicit On
' espacios de nombres
Imports System
Module test
Sub Main()
' un profesor
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' una persona
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' muestra
Sub affiche(ByVal p As personne)
' muestra identidad de p
Console.Out.WriteLine(p.identite)
End Sub
End Module
produce entonces los siguientes resultados:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant(personne(Lucile,Dumas,56),61)
Construction personne(string, string, int)
personne(Jean,Dupont,30)
Esta vez sí que hemos obtenido la identidad completa del profesor. Ahora redefinamos un método en lugar de una propiedad. La clase object es la clase «madre» de todas las clases VB.NET. Así, cuando escribimos:
se escribe implícitamente:
La clase object define un método virtual ToString:

El método ToString devuelve el nombre de la clase a la que pertenece el objeto, tal y como se muestra en el siguiente ejemplo:
' opciones
Option Strict On
Option Explicit On
' espacios de nombres
Imports System
Module test2
Sub Main()
' un profesor
Console.Out.WriteLine(New enseignant("Lucile", "Dumas", 56, 61).ToString())
' una persona
Console.Out.WriteLine(New personne("Jean", "Dupont", 30).ToString())
End Sub
End Module
Los resultados obtenidos son los siguientes:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant
Construction personne(string, string, int)
personne
Cabe señalar que, aunque no hayamos redefinido el método ToString en las clases personne y enseignant, se puede observar que el método ToString de la clase object es capaz de mostrar el nombre real de la clase del objeto. Redefinamos el método ToString en las clases personne y enseignant:
' ToString
Public Overrides Function ToString() As String
' se establece la propiedad «identidad»
Return identite
End Function
La definición es la misma en ambas clases. Consideremos el siguiente programa de prueba:
' opciones
Option Strict On
Option Explicit On
' espacios de nombres
Imports System
Module test
Sub Main()
' un profesor
Dim e As New enseignant("Lucile", "Dumas", 56, 61)
affiche(e)
' una persona
Dim p As New personne("Jean", "Dupont", 30)
affiche(p)
End Sub
' muestra
Sub affiche(ByVal p As personne)
' muestra identidad de p
Console.Out.WriteLine(p.identite)
End Sub
End Module
Los resultados de la ejecución son los siguientes:
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
enseignant(personne(Lucile,Dumas,56),61)
Construction personne(string, string, int)
personne(Jean,Dupont,30)
3.3. Definir un indexador para una clase
Consideremos la clase [ArrayList] predefinida en la plataforma .NET. Esta clase permite almacenar objetos en una lista. Pertenece al espacio de nombres [System.Collections]. En el siguiente ejemplo, utilizamos esta clase para almacenar una lista de personas (en sentido amplio):
' opciones
Option Strict On
Option Explicit On
' espacios de nombres
Imports System
Imports System.Collections
' clase listeDePersonnes
Public Class listeDePersonnes
Inherits ArrayList
' para añadir una persona a la lista
Public Overloads Sub Add(ByVal p As personne)
MyBase.Add(p)
End Sub
' un indexador
Default Public Shadows Property Item(ByVal i As Integer) As personne
Get
Return CType(MyBase.Item(i), personne)
End Get
Set(ByVal Value As personne)
MyBase.Item(i) = Value
End Set
End Property
' otro indexador
Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer
Get
' se busca a la persona llamada N
Dim i As Integer
For i = 0 To Count - 1
If CType(Me(i), personne).nom = N Then
Return i
End If
Next i
Return -1
End Get
End Property
' toString
Public Overrides Function ToString() As String
' devuelve (el1, el2, ..., eln)
Dim liste As String = "("
Dim i As Integer
' se recorre la matriz dinámica
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' último elemento
If Count <> 0 Then
liste += "[" + Me(i).ToString + "]"
End If
liste += ")"
Return liste
End Function
End Class
Veamos algunos detalles sobre ciertas propiedades y métodos de la clase [ArrayList]:
atributo que indica el número de elementos de la lista | |
método que permite añadir un objeto a la lista | |
método que devuelve el elemento i de la lista |
Observamos que, para obtener el elemento n.º i de la lista, no hemos escrito [liste.Item(i)], sino directamente [liste(i)], lo que a primera vista parece erróneo. Sin embargo, esto es posible porque la clase [ArrayList] define una propiedad por defecto [Item] con una sintaxis similar a la siguiente:
Default Public Property Item(ByVal i As Integer) As Object
Get
...
End Get
Set(ByVal Value As personne)
...
End Set
End Property
Cuando el compilador encuentra la notación [liste(i)], comprueba si la clase [ArrayList] ha definido una propiedad con la siguiente firma:
Default Public Property Proc(ByVal var As Integer) As Type
Aquí encontrará el procedimiento [Item]. A continuación, traducirá la escritura [liste(i)] a [liste.Item(i)]. La propiedad [Item] se denomina propiedad indexada por defecto de la clase [ArrayList]. La ejecución del programa anterior da los siguientes resultados:
dos>vbc /r:personne.dll /r:enseignant.dll lstpersonnes1.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
11/03/2004 18:26 3 584 enseignant.dll
12/03/2004 15:34 3 584 lstpersonnes1.exe
12/03/2004 13:39 661 lstpersonnes1.vb
11/03/2004 18:26 4 096 personne.dll
dos>lstpersonnes1
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
personne(paul,chenou,31)
personne(nicole,chenou,11)
enseignant(personne(jacques,sileau,33),61)
Creemos una clase llamada [listeDePersonnes] que sea una lista de personas, es decir, una lista específica que parece natural derivar de la clase [ArrayList]:
' opciones
Option Strict On
Option Explicit On
' espacios de nombres
Imports System
Imports System.Collections
' clase listeDePersonnes
Public Class listeDePersonnes
Inherits ArrayList
' para añadir una persona a la lista
Public Shadows Sub Add(ByVal p As Object)
' p debe ser una persona
If Not TypeOf (p) Is personne Then
Throw New Exception("L'objet ajouté (" + p.ToString + ") n'est pas une personne")
Else
MyBase.Add(p)
End If
End Sub
' un indexador
Default Public Shadows Property Index(ByVal i As Integer) As personne
Get
Return CType(MyBase.Item(i), personne)
End Get
Set(ByVal Value As personne)
MyBase.Item(i) = Value
End Set
End Property
' toString
Public Overrides Function ToString() As String
' devuelve (el1, el2, ..., eln)
Dim liste As String = "("
Dim i As Integer
' recorremos la matriz dinámica
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' último elemento
If Count <> 0 Then
liste += "[" + Me(i).ToString + "]"
End If
liste += ")"
Return liste
End Function
End Class
La clase presenta los siguientes métodos y propiedades:
devuelve una cadena de caracteres que «representa» el contenido de la lista | |
método que permite añadir una persona a la lista | |
propiedad indexada por defecto que devuelve a la persona i de la lista |
Veamos las novedades:
Se ha creado un nuevo método [Add].
' para añadir una persona a la lista
Public Shadows Sub Add(ByVal p As Object)
' p debe ser una persona
If Not TypeOf (p) Is personne Then
Throw New Exception("L'objet ajouté (" + p.ToString + ") n'est pas une personne")
Else
MyBase.Add(p)
End If
End Sub
Ya existe uno en la clase padre [ArrayList] con la misma firma, de ahí la palabra clave [Shadows] para indicar que el nuevo procedimiento sustituye al de la clase padre. El método [Add] de la clase hija comprueba que el objeto añadido sea efectivamente de tipo [personne] o un derivado con la función [TypeOf]. Si no es así, se lanza una excepción con la instrucción [Throw]. Si el objeto añadido es efectivamente de tipo [personne], se utiliza el método [Add] de la clase base para realizar la adición.
Se crea una propiedad indexada:
' un índice
Default Public Shadows Property Index(ByVal i As Integer) As personne
Get
Return CType(MyBase.Item(i), personne)
End Get
Set(ByVal Value As personne)
MyBase.Item(i) = Value
End Set
End Property
Ya existe una propiedad predeterminada llamada [Item] en la clase base con la misma firma. Por lo tanto, es necesario utilizar la palabra clave [Shadows] para indicar que la nueva propiedad indexada [Index] «ocultará» la de la clase base [Item]. Cabe señalar que esto es así incluso si las dos propiedades no tienen el mismo nombre. La propiedad [Index] permite hacer referencia a la persona n.º i de la lista. Se basa en la propiedad [Item] de la clase base para acceder al elemento n.º i del objeto subyacente [ArrayList]. Se realizan cambios de tipo para tener en cuenta que la propiedad [Item] trabaja con elementos de tipo [Object], mientras que la propiedad [Index] trabaja con elementos de tipo [personne].
Por último, redefinimos (Overrides) el método [ToString] de la clase [ArrayList]:
' toString
Public Overrides Function ToString() As String
' devuelve (el1, el2, ..., eln)
Dim liste As String = "("
Dim i As Integer
' recorremos la matriz dinámica
For i = 0 To (Count - 2)
liste += "[" + Me(i).ToString + "]" + ","
Next i 'for
' último elemento
If Count <> 0 Then
liste += "[" + Me(i).ToString + "]"
End If
liste += ")"
Return liste
End Function
Este método devuelve una cadena de caracteres con el formato «(e1,e2,...,en)», donde e1, e2, ..., en son los elementos de la lista. Cabe destacar la notación [Me(i)], que designa el elemento n.º i del objeto actual [Me]. Se utiliza la propiedad indexada por defecto. Así, [Me(i)] es equivalente a [Me.Index(i)].
El código de la clase se coloca en el archivo [lstpersonnes2.vb] y se compila:
dos>dir
11/03/2004 18:26 3 584 enseignant.dll
12/03/2004 15:45 970 lstpersonnes2.vb
11/03/2004 18:26 4 096 personne.dll
dos>vbc /r:personne.dll /r:enseignant.dll /t:library lstpersonnes2.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
11/03/2004 18:26 3 584 enseignant.dll
12/03/2004 15:50 3 584 lstpersonnes2.dll
12/03/2004 15:45 970 lstpersonnes2.vb
11/03/2004 18:26 4 096 personne.dll
Se crea un programa de prueba:
' opciones
Option Explicit On
Option Strict On
' espacios de nombres
Imports System
Imports System.Collections
Module test
Sub Main()
' creación de una lista vacía de personas
Dim liste As listeDePersonnes = New listeDePersonnes
' creación de personas
Dim p1 As personne = New personne("paul", "chenou", 31)
Dim p2 As personne = New personne("nicole", "chenou", 11)
Dim e1 As enseignant = New enseignant("jacques", "sileau", 33, 61)
' rellenar la lista
liste.Add(p1)
liste.Add(p2)
liste.Add(e1)
' visualización de la lista
Console.Out.WriteLine(liste.ToString)
' Añadir un objeto distinto de persona
Try
liste.Add(4)
Catch e As Exception
Console.Error.WriteLine(e.Message)
End Try
End Sub
End Module
Se compila:
dos>vbc /r:personne.dll /r:enseignant.dll /r:lstpersonnes2.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
11/03/2004 18:26 3 584 enseignant.dll
12/03/2004 15:50 3 584 lstpersonnes2.dll
12/03/2004 15:45 970 lstpersonnes2.vb
11/03/2004 18:26 4 096 personne.dll
12/03/2004 15:50 3 584 test.exe
12/03/2004 15:49 623 test.vb
y luego ejecutado:
dos>test
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction personne(string, string, int)
Construction enseignant(string,string,int,int)
([personne(paul,chenou,31)],[personne(nicole,chenou,11)],[enseignant(personne(jacques,sileau,33),61)])
L'objet ajouté (4) n'est pas une personne
Podríamos querer escribir
donde l sería del tipo [listeDePersonnes]. En este caso, queremos indexar la lista l no por un número de elemento, sino por el nombre de una persona. Para ello, definimos una nueva propiedad indexada por defecto:
' otro indexador
Default Public Shadows ReadOnly Property Item(ByVal N As String) As Integer
Get
' se busca a la persona llamada N
Dim i As Integer
For i = 0 To Count - 1
If CType(Me(i), personne).nom = N Then
Return i
End If
Next i
Return -1
End Get
End Property
La primera línea
Default Public Shadows ReadOnly Property Index(ByVal N As String) As Integer
indica que se vuelve a crear una propiedad indexada por defecto. Todas las propiedades por defecto deben tener el mismo nombre, en este caso [Index]. La nueva propiedad [Index] indexa la clase listeDePersonnes mediante una cadena de caracteres N. El resultado de listeDePersonnes(N) es un entero. Este entero será la posición en la lista de la persona con el nombre N o -1 si dicha persona no está en la lista. Solo se define la propiedad get, lo que impide la escritura de listeDePersonnes ("nom")=valeur, que habría requerido la definición de la propiedad set). De ahí la palabra clave [ReadOnly]. La palabra clave [Shadows] es necesaria para ocultar la propiedad por defecto de la clase base (aunque no tenga la misma firma).
En el cuerpo de get, se recorre la lista de personas en busca del nombre N pasado como parámetro. Si se encuentra en la posición i, se devuelve i; de lo contrario, se devuelve -1.
Un nuevo programa de prueba podría ser el siguiente:
' opciones
Option Strict On
Option Explicit On
' espacios de nombres
Imports System
' página de prueba
Module test
Sub Main()
' una lista de personas
Dim l As New listeDePersonnes
' Añadir personas
l.Add(New personne("jean", "dumornet", 10))
l.Add(New personne("pauline", "duchemin", 12))
' visualización
Console.Out.WriteLine(("l=" + l.ToString))
l.Add(New personne("jacques", "tartifume", 27))
Console.Out.WriteLine(("l=" + l.ToString))
' cambio del elemento 1
l(1) = New personne("sylvie", "cachan", 5)
' visualización del elemento 1
Console.Out.WriteLine(("l[1]=" + l(1).ToString))
' visualización de la lista l
Console.Out.WriteLine(("l=" + l.ToString))
' búsqueda de personas
Dim noms() As String = New [String]() {"cachan", "inconnu"}
Dim i As Integer
For i = 0 To noms.Length - 1
Dim inom As Integer = l(noms(i))
If inom <> -1 Then
Console.Out.WriteLine(("personne(" & noms(i) & ")=" & l(inom).ToString))
Else
Console.Out.WriteLine(("personne(" + noms(i) + ") n'existe pas"))
End If
Next i
End Sub
End Module
La ejecución da los siguientes resultados:
personne(string, string, int)
Construction personne(string, string, int)
l=([personne(jean,dumornet,10)],[personne(pauline,duchemin,12)])
Construction personne(string, string, int)
l=([personne(jean,dumornet,10)],[personne(pauline,duchemin,12)],[personne(jacques,tartifume,27)])
Construction personne(string, string, int)
l[1]=personne(sylvie,cachan,5)
l=([personne(jean,dumornet,10)],[personne(sylvie,cachan,5)],[personne(jacques,tartifume,27)])
personne(cachan)=personne(sylvie,cachan,5)
personne(inconnu) n'existe pas
3.4. Las estructuras
La estructura VB.NET se deriva directamente de la estructura del lenguaje C y es muy similar a una clase. Una estructura se define de la siguiente manera:
Structure spersonne
' atributos
...
' propiedades
...
' constructores
...
' métodos
End Structure
A pesar de la similitud en su declaración, existen diferencias importantes entre las clases y las estructuras. Por ejemplo, el concepto de herencia no existe en las estructuras. Si escribimos una clase que no debe derivarse, ¿cuáles son las diferencias entre una estructura y una clase que nos ayudarán a elegir entre ambas? Veamos el siguiente ejemplo para descubrirlo:
' opciones
Option Strict On
Option Explicit On
' espacios de nombres
Imports System
' estructura spersonne
Structure spersonne
Public nom As String
Public age As Integer
End Structure
' clase persona
Class cpersonne
Public nom As String
Public age As Integer
End Class
' un módulo de prueba
Public Module test
Sub Main()
' una persona p1
Dim sp1 As spersonne
sp1.nom = "paul"
sp1.age = 10
Console.Out.WriteLine(("sp1=spersonne(" & sp1.nom & "," & sp1.age & ")"))
' una persona p2
Dim sp2 As spersonne = sp1
Console.Out.WriteLine(("sp2=spersonne(" & sp2.nom & "," & sp2.age & ")"))
' sp2 se modifica
sp2.nom = "nicole"
sp2.age = 30
' verificación de sp1 y sp2
Console.Out.WriteLine(("sp1=cpersonne(" & sp1.nom & "," & sp1.age & ")"))
Console.Out.WriteLine(("sp2=cpersonne(" & sp2.nom & "," & sp2.age & ")"))
' una persona cp1
Dim cp1 As New cpersonne
cp1.nom = "paul"
cp1.age = 10
Console.Out.WriteLine(("cP1=cpersonne(" & cp1.nom & "," & cp1.age & ")"))
' una persona cp2 P2
Dim cp2 As cpersonne = cp1
Console.Out.WriteLine(("cP2=cpersonne(" & cp2.nom & "," & cp2.age & ")"))
' P2 se modifica
cp2.nom = "nicole"
cp2.age = 30
' verificación de P1 y P2
Console.Out.WriteLine(("cP1=cpersonne(" & cp1.nom & "," & cp1.age & ")"))
Console.Out.WriteLine(("cP2=cpersonne(" & cp2.nom & "," & cp2.age & ")"))
End Sub
End Module
Si ejecutamos este programa, obtenemos los siguientes resultados:
sp1=spersonne(paul,10)
sp2=spersonne(paul,10)
sp1=cpersonne(paul,10)
sp2=cpersonne(nicole,30)
cP1=cpersonne(paul,10)
cP2=cpersonne(paul,10)
cP1=cpersonne(nicole,30)
cP2=cpersonne(nicole,30)
Mientras que en las páginas anteriores de este capítulo se utilizaba una clase personne, ahora utilizamos una estructura spersonne:
' estructura de persona
Structure spersonne
Public nom As String
Public age As Integer
End Structure
La declaración
Dim sp1 As spersonne
crea una estructura (nombre, edad) y el valor de sp1 es esta misma estructura.
La declaración
Dim cp1 As New cpersonne
crea un objeto [cpersonne] (más o menos el equivalente a nuestra estructura) y cp1 es entonces la dirección (la referencia) de este objeto.
Resumamos
- en el caso de la estructura, el valor de sp1 es la propia estructura
- en el caso de la clase, el valor de p1 es la dirección del objeto creado
![]() |
![]() |
Cuando en el programa se escribe
Dim sp2 As spersonne = sp1
se crea una nueva estructura (nombre, edad) y se inicializa con el valor de p1, es decir, la propia estructura.
![]() |
La estructura de sp1 se duplica, por tanto, en sp2. Se trata de una copia de valores.
La instrucción
Dim cp2 As cpersonne = cp1
funciona de manera diferente. El valor de cp1 se copia en cp2, pero como ese valor es, en realidad, la dirección del objeto, este no se duplica. Simplemente tiene dos referencias que apuntan a él:
![]() |
En el caso de la estructura, si se modifica el valor de sp2, no se modifica el valor de sp1, tal y como muestra el programa. En el caso del objeto, si se modifica el objeto al que apunta cp2, el que apunta cp1 se modifica, ya que es el mismo. Esto es lo que muestran también los resultados del programa.
De estas explicaciones se deduce, por tanto, que:
- el valor de una variable de tipo estructura es la propia estructura
- el valor de una variable de tipo objeto es la dirección del objeto al que apunta
Una vez comprendida esta diferencia fundamental, la estructura resulta muy similar a la clase, como muestra el siguiente ejemplo:
' opciones
Option Strict On
Option Explicit On
' espacios de nombres
Imports System
' estructura de persona
Structure personne
' atributos
Private _nom As String
Private _age As Integer
' propiedades
Public Property nom() As String
Get
Return _nom
End Get
Set(ByVal Value As String)
_nom = value
End Set
End Property
Public Property age() As Integer
Get
Return _age
End Get
Set(ByVal Value As Integer)
_age = value
End Set
End Property
' Constructor
Public Sub New(ByVal NOM As String, ByVal AGE As Integer)
_nom = NOM
_age = AGE
End Sub 'New
' TOSTRING
Public Overrides Function ToString() As String
Return "personne(" & nom & "," & age & ")"
End Function
End Structure
' un módulo de prueba
Module test
Sub Main()
' una persona p1
Dim p1 As New personne("paul", 10)
Console.Out.WriteLine(("p1=" & p1.ToString))
' una persona p2
Dim p2 As personne = p1
Console.Out.WriteLine(("p2=" & p2.ToString))
' p2 se modifica
p2.nom = "nicole"
p2.age = 30
' verificación de p1 y p2
Console.Out.WriteLine(("p1=" & p1.ToString))
Console.Out.WriteLine(("p2=" & p2.ToString))
End Sub
End Module
Se obtienen los siguientes resultados de ejecución:
La única diferencia notable aquí entre la estructura y la clase es que, con una clase, los objetos p1 y p2 habrían tenido el mismo valor al final del programa, el de p2.
3.5. Las interfaces
Una interfaz es un conjunto de prototipos de métodos o propiedades que forman un contrato. Una clase que decide implementar una interfaz se compromete a proporcionar una implementación de todos los métodos definidos en la interfaz. Es el compilador quien verifica esta implementación. A continuación se muestra, por ejemplo, la definición de la interfaz Istats:
Cualquier clase que implemente esta interfaz se declarará como
public class C
Implements Istats
...
function moyenne() as Double Implements Istats.moyenne
...
end function
function écartType () as Double Implements Istats. écartType
...
end function
end class
Los métodos [moyenne] y [écartType] deberán definirse en la clase C. Consideremos el siguiente código:
' opciones
Option Strict On
Option Explicit On
' espacios de nombres
Imports System
' estructura
Public Structure élève
Public _nom As String
Public _note As Double
' constructor
Public Sub New(ByVal NOM As String, ByVal NOTE As Double)
Me._nom = NOM
Me._note = NOTE
End Sub
End Structure
' clase notas
Public Class notes
' atributo
Protected _matière As String
Protected _élèves() As élève
' constructor
Public Sub New(ByVal MATIERE As String, ByVal ELEVES() As élève)
' almacenamiento de alumnos y asignaturas
Me._matière = MATIERE
Me._élèves = ELEVES
End Sub
' ToString
Public Overrides Function ToString() As String
Dim valeur As String = "matière=" + _matière + ", notes=("
Dim i As Integer
' se concatenan todas las notas
For i = 0 To (_élèves.Length - 1) - 1
valeur &= "[" & _élèves(i)._nom & "," & _élèves(i)._note & "],"
Next i
'última nota
If _élèves.Length <> 0 Then
valeur &= "[" & _élèves(i)._nom & "," & _élèves(i)._note & "]"
End If
valeur += ")"
' fin
Return valeur
End Function
End Class
La clase notes recopila las notas de una clase en una asignatura:
Public Class notes
' atributo
Protected _matière As String
Protected _élèves() As élève
Los atributos se declaran como protected para que sean accesibles desde una clase derivada. El tipo élève es una estructura que almacena el nombre del alumno y su nota en la asignatura:
Public Structure élève
Public _nom As String
Public _note As Double
' constructor
Public Sub New(ByVal NOM As String, ByVal NOTE As Double)
Me._nom = NOM
Me._note = NOTE
End Sub
End Structure
Decidimos derivar esta clase notes en una clase notesStats que tendría dos atributos adicionales, la media y la desviación estándar de las notas:
Public Class notesStats
Inherits notes
Implements Istats
' atributos
Private _moyenne As Double
Private _écartType As Double
La clase notesStats implementa la siguiente interfaz Istats:
Esto significa que la clase notesStats debe tener dos métodos llamados moyenne y écartType con la firma indicada en la interfaz Istats. La clase notesStats es la siguiente:
' opciones
Option Strict On
Option Explicit On
' espacios de nombres
Imports System
Public Class notesStats
Inherits notes
Implements Istats
' atributos
Private _moyenne As Double
Private _écartType As Double
' constructor
Public Sub New(ByVal MATIERE As String, ByVal ELEVES() As élève)
MyBase.New(MATIERE, ELEVES)
' cálculo de la media de las puntuaciones
Dim somme As Double = 0
Dim i As Integer
For i = 0 To ELEVES.Length - 1
somme += ELEVES(i)._note
Next i
If ELEVES.Length <> 0 Then
_moyenne = somme / ELEVES.Length
Else
_moyenne = -1
End If
' desviación estándar
Dim carrés As Double = 0
For i = 0 To ELEVES.Length - 1
carrés += Math.Pow(ELEVES(i)._note - _moyenne, 2)
Next i
If ELEVES.Length <> 0 Then
_écartType = Math.Sqrt((carrés / ELEVES.Length))
Else
_écartType = -1
End If
End Sub
' ToString
Public Overrides Function ToString() As String
Return MyBase.ToString() & ",moyenne=" & _moyenne & ",écart-type=" & _écartType
End Function 'ToString
' métodos de la interfaz Istats
Public Function moyenne() As Double Implements Istats.moyenne
' devuelve la media de las puntuaciones
Return _moyenne
End Function
Public Function écartType() As Double Implements Istats.écartType
' devuelve la desviación estándar
Return _écartType
End Function
End Class
La media _moyenne y la desviación estándar _ecartType se calculan en el momento de la construcción del objeto. Por lo tanto, los métodos moyenne y écartType solo tienen que devolver el valor de los atributos _moyenne y _ecartType. Ambos métodos devuelven -1 si la tabla de alumnos está vacía.
La siguiente clase de prueba:
' opciones
Option Strict On
Option Explicit On
' espacios de nombres
Imports System
Module test
Sub Main()
' algunos alumnos y notas
Dim ELEVES() As élève = {New élève("paul", 14), New élève("nicole", 16), New élève("jacques", 18)}
' que se guardan en un objeto de notas
Dim anglais As New notes("anglais", ELEVES)
' y que se muestran
Console.Out.WriteLine((anglais.ToString))
' lo mismo con la media y la desviación estándar
anglais = New notesStats("anglais", ELEVES)
Console.Out.WriteLine((anglais.ToString))
End Sub
End Module
da los siguientes resultados:
matière=anglais, notes=([paul,14],[nicole,16],[jacques,18])
matière=anglais, notes=([paul,14],[nicole,16],[jacques,18]),moyenne=16,écart-type=1,63299316185545
La clase notesStats podría perfectamente haber implementado los métodos moyenne y écartType por sí misma sin indicar que implementaba la interfaz Istats. ¿Cuál es la utilidad de las interfaces? Es la siguiente: una función puede admitir como parámetro formal un dato que tenga el tipo de una interfaz I. Cualquier objeto de una clase C que implemente la interfaz I podrá entonces ser un parámetro efectivo de dicha función. Consideremos el siguiente ejemplo:
' opciones
Option Strict On
Option Explicit On
' espacios de nombres
Imports System
' una interfaz Iejemplo
Public Interface Iexemple
Function ajouter(ByVal i As Integer, ByVal j As Integer) As Integer
Function soustraire(ByVal i As Integer, ByVal j As Integer) As Integer
End Interface
' una primera clase
Public Class classe1
Implements Iexemple
Public Function ajouter(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.ajouter
Return a + b + 10
End Function
Public Function soustraire(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.soustraire
Return a - b + 20
End Function
End Class
'una segunda clase
Public Class classe2
Implements Iexemple
Public Function ajouter(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.ajouter
Return a + b + 100
End Function
Public Function soustraire(ByVal a As Integer, ByVal b As Integer) As Integer Implements Iexemple.soustraire
Return a - b + 200
End Function
End Class
La interfaz Iexemple define dos métodos: ajouter y soustraire. Las clases classe1 y classe2 implementan esta interfaz. Cabe señalar que estas clases no hacen nada más, con el fin de simplificar el ejemplo. Consideremos ahora el siguiente ejemplo:
' opciones
Option Strict On
Option Explicit On
' espacios de nombres
Imports System
' clase de prueba
Module test
'calcular
Sub calculer(ByVal i As Integer, ByVal j As Integer, ByVal inter As Iexemple)
Console.Out.WriteLine(inter.ajouter(i, j))
Console.Out.WriteLine(inter.soustraire(i, j))
End Sub
' la función Main
Sub Main()
' creación de dos objetos clase1 y clase2
Dim c1 As New classe1
Dim c2 As New classe2
' llamadas a la función estática calcular
calculer(4, 3, c1)
calculer(14, 13, c2)
End Sub
End Module
La función calculer admite como parámetro un elemento de tipo Iexemple. Por lo tanto, podrá recibir para este parámetro tanto un objeto de tipo classe1 como de tipo classe2. Esto es lo que se hace en el procedimiento Main con los siguientes resultados:
Se observa, por tanto, que se trata de una propiedad similar al polimorfismo visto en las clases. Si un conjunto de clases Ci no relacionadas entre sí por herencia (por lo que no se puede utilizar el polimorfismo de la herencia) presenta un conjunto de métodos con la misma firma, puede resultar interesante agrupar estos métodos en una interfaz I de la que heredarían todas las clases en cuestión. Las instancias de estas clases Ci pueden entonces utilizarse como parámetros de funciones que admiten un parámetro de tipo I, c.a.d. Funciones que utilizan únicamente los métodos de los objetos Ci definidos en la interfaz I y no los atributos y métodos particulares de las diferentes clases Ci. Por último, cabe señalar que la herencia de interfaces puede ser múltiple, c.a.d. que se puede escribir
Public Class classe
Implements I1,I2,...
donde los Ij son interfaces.
3.6. Los espacios de nombres
Para escribir una línea en pantalla, utilizamos la instrucción
Si observamos la definición de la clase Console
Namespace: System
Assembly: Mscorlib (in Mscorlib.dll)
descubrimos que forma parte del espacio de nombres System. Esto significa que la clase Console debería designarse como System.Console y, de hecho, deberíamos escribir:
Esto se evita utilizando una cláusula imports:
Se dice que se importa el espacio de nombres System mediante la cláusula imports. Cuando el compilador encuentre el nombre de una clase (en este caso, Console), la buscará en los distintos espacios de nombres importados por las cláusulas imports. Aquí encontrará la clase Console en el espacio de nombres System. Observemos ahora la segunda información asociada a la clase Console:
Assembly: Mscorlib (in Mscorlib.dll)
Esta línea indica en qué «ensamblado» se encuentra la definición de la clase Console. Cuando se compila fuera de Visual Studio.NET y hay que proporcionar las referencias de los diferentes dll que contienen las clases que se deben utilizar, esta información puede resultar útil. Recordemos que para referenciar los dll necesarios para la compilación de una clase, se escribe:
Al crear una clase, se puede crear dentro de un espacio de nombres. El objetivo de estos espacios de nombres es evitar conflictos de nombres entre clases cuando estas se comercializan, por ejemplo. Consideremos dos empresas, E1 y E2, que distribuyen clases empaquetadas respectivamente en dll, E1.dll y E2.dll. Supongamos que un cliente C compra estos dos conjuntos de clases en los que ambas empresas han definido una clase personne. El cliente C compila un programa de la siguiente manera:
Si el código fuente prog.vb utiliza la clase personne, el compilador no sabrá si debe tomar la clase personne de E1.dll o la de E2.dll. Notificará un error. Si la empresa E1 se encarga de crear sus clases en un espacio de nombres llamado E1 y la empresa E2 en un espacio de nombres llamado E2, las dos clases personne se llamarán entonces E1.personne y E2.personne. El cliente deberá utilizar en sus clases E1.personne o E2.personne, pero no personne. El espacio de nombres permite eliminar la ambigüedad. Para crear una clase en un espacio de nombres, se escribe:
Namespace istia.st
Public Class personne
' definición de la clase
...
end Class
end Namespace
A modo de ejemplo, creemos en un espacio de nombres nuestra clase personne estudiada anteriormente. Elegiremos istia.st como espacio de nombres. La clase personne pasa a ser:
' opciones
Option Strict On
Option Explicit On
' espacios de nombres
Imports System
' creación del espacio de nombres istia.st
Namespace istia.st
Public Class personne
' atributos
Private prenom As String
Private nom As String
Private age As Integer
' método
Public Sub initialise(ByVal P As String, ByVal N As String, ByVal age As Integer)
Me.prenom = P
Me.nom = N
Me.age = age
End Sub
' método
Public Sub identifie()
Console.Out.WriteLine((prenom & "," & nom & "," & age))
End Sub
End Class
End Namespace
Esta clase se compila en personne.dll:
dos>vbc /t:library personne.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
Ahora utilicemos la clase personne en una clase de prueba:
' opciones
Option Strict On
Option Explicit On
' espacios de nombres
Imports System
Imports istia.st
' página de prueba
Public Module test
Sub Main()
Dim p1 As New personne
p1.initialise("Jean", "Dupont", 30)
p1.identifie()
End Sub
End Module
Para evitar escribir
Dim p1 As New istia.st.personne
hemos importado el espacio de nombres istia.st con una cláusula imports:
Imports istia.st
Ahora compilemos el programa de prueba:
dos>dir
12/03/2004 18:06 3 584 personne.dll
11/03/2004 18:27 610 personne.vb
12/03/2004 18:05 254 test.vb
dos>vbc /r:personne.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
12/03/2004 18:06 3 584 personne.dll
11/03/2004 18:27 610 personne.vb
12/03/2004 18:08 3 072 test.exe
12/03/2004 18:05 254 test.vb
Esto genera un archivo test.exe que, al ejecutarse, ofrece los siguientes resultados:
3.7. El ejemplo IMPOTS
Retomamos el cálculo del impuesto ya estudiado en el capítulo anterior y lo procesamos utilizando una clase. Recordemos el problema:
Nos situamos en el caso simplificado de un contribuyente que solo tiene que declarar su salario:
- se calcula el número de partes del asalariado nbParts=nbEnfants/2 +1 si no está casado, nbEnfants/2+2 si está casado, donde nbEnfants es su número de hijos.
- si tiene al menos tres hijos, tiene media parte más
- se calcula su renta imponible R=0,72*S, donde S es su salario anual
- se calcula su coeficiente familiar QF = R / nbParts
- se calcula su impuesto I. Consideremos la siguiente tabla:
12620,0 | 0 | 0 |
13190 | 0,05 | 631 |
15640 | 0,1 | 1290,5 |
24 740 | 0,15 | 2072,5 |
31 810 | 0,2 | 3309,5 |
39 970 | 0,25 | 4900 |
48 360 | 0,3 | 6898,5 |
55 790 | 0,35 | 9316,5 |
92 970 | 0,4 | 12106 |
127860 | 0,45 | 16754,5 |
151 250 | 0,50 | 231 47,5 |
172 040 | 0,55 | 30710 |
195 000 | 0,60 | 39312 |
0 | 0,65 | 49062 |
Cada línea tiene 3 campos. Para calcular el impuesto I, se busca la primera línea donde QF<=campo1. Por ejemplo, si QF=23000, se encontrará la línea
El impuesto I es entonces igual a 0,15*R - 2072,5*nbParts. Si QF es tal que la relación QF<=campo1 nunca se cumple, entonces se utilizan los coeficientes de la última línea. En este caso:
lo que da el impuesto I=0,65*R - 49062*nbParts.
La clase de impuesto se definirá de la siguiente manera:
' opciones
Option Strict On
Option Explicit On
' espacios de nombres
Imports System
Public Class impot
' los datos necesarios para el cálculo del impuesto
' proceden de una fuente externa
Private limites(), coeffR(), coeffN() As Decimal
' fabricante
Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
' se comprueba que las 3 tablas tengan el mismo tamaño
Dim OK As Boolean = LIMITES.Length = COEFFR.Length And LIMITES.Length = COEFFN.Length
If Not OK Then
Throw New Exception("Les 3 tableaux fournis n'ont pas la même taille(" & LIMITES.Length & "," & COEFFR.Length & "," & COEFFN.Length & ")")
End If
' Todo correcto
Me.limites = LIMITES
Me.coeffR = COEFFR
Me.coeffN = COEFFN
End Sub
' cálculo del impuesto
Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Long) As Long
' cálculo del número de participaciones
Dim nbParts As Decimal
If marié Then
nbParts = CDec(nbEnfants) / 2 + 2
Else
nbParts = CDec(nbEnfants) / 2 + 1
End If
If nbEnfants >= 3 Then
nbParts += 0.5D
End If
' cálculo de la renta imponible y del coeficiente familiar
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' cálculo del impuesto
limites((limites.Length - 1)) = QF + 1
Dim i As Integer = 0
While QF > limites(i)
i += 1
End While
Return CLng(revenu * coeffR(i) - nbParts * coeffN(i))
End Function
End Class
Se crea un objeto impuesto con los datos que permiten calcular el impuesto de un contribuyente. Esta es la parte estable del objeto. Una vez creado este objeto, se puede llamar repetidamente a su método calcular, que calcula el impuesto del contribuyente a partir de su estado civil (casado o soltero), su número de hijos y su salario anual. Un programa de prueba podría ser el siguiente:
' opciones
Option Strict On
Option Explicit On
' espacios de nombres
Imports System
Imports Microsoft.VisualBasic
Module test
Sub Main()
' programa interactivo de cálculo de impuestos
' el usuario introduce tres datos con el teclado: casado nbEnfants salario
' el programa muestra entonces el impuesto a pagar
Const syntaxe As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"
' tablas de datos necesarias para el cálculo del impuesto
Dim limites() As Decimal = {12620D, 13190D, 15640D, 24740D, 31810D, 39970D, 48360D, 55790D, 92970D, 127860D, 151250D, 172040D, 195000D, 0D}
Dim coeffR() As Decimal = {0D, 0.05D, 0.1D, 0.15D, 0.2D, 0.25D, 0.3D, 0.35D, 0.4D, 0.45D, 0.5D, 0.55D, 0.6D, 0.65D}
Dim coeffN() As Decimal = {0D, 631D, 1290.5D, 2072.5D, 3309.5D, 4900D, 6898.5D, 9316.5D, 12106D, 16754.5D, 23147.5D, 30710D, 39312D, 49062D}
' creación de un objeto de impuesto
Dim objImpôt As impot = Nothing
Try
objImpôt = New impot(limites, coeffR, coeffN)
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(1)
End Try
' bucle infinito
Dim marié As String
Dim nbEnfants As Integer
Dim salaire As Long
While True
' se solicitan los parámetros para el cálculo del impuesto
Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
Dim paramètres As String = Console.In.ReadLine().Trim()
' ¿Hay algo que hacer?
If paramètres Is Nothing OrElse paramètres = "" Then
Exit While
End If
' verificación del número de argumentos en la línea introducida
Dim erreur As Boolean = False
Dim args As String() = paramètres.Split(Nothing)
Dim nbParamètres As Integer = args.Length
If nbParamètres <> 3 Then
Console.Error.WriteLine(syntaxe)
erreur = True
End If
' verificación de la validez de los parámetros
If Not erreur Then
' casado
marié = args(0).ToLower()
If marié <> "o" And marié <> "n" Then
erreur = True
End If
' nbEnfants
Try
nbEnfants = Integer.Parse(args(1))
If nbEnfants < 0 Then
Throw New Exception
End If
Catch
erreur = True
End Try
' salario
Try
salaire = Integer.Parse(args(2))
If salaire < 0 Then
Throw New Exception
End If
Catch
erreur = True
End Try
End If
' si los parámetros son correctos, se calcula el impuesto
If Not erreur Then
Console.Out.WriteLine(("impôt=" & objImpôt.calculer(marié = "o", nbEnfants, salaire) & " F"))
Else
Console.Error.WriteLine(syntaxe)
End If
End While
End Sub
End Module
A continuación se muestra un ejemplo de ejecución del programa anterior:
dos>vbc /t:library impots.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dir>dir
12/03/2004 18:24 4 096 impots.dll
12/03/2004 18:20 1 483 impots.vb
12/03/2004 18:21 2 805 test.vb
dos>vbc /r:impots.dll test.vb
Compilateur Microsoft (R) Visual Basic .NET version 7.10.3052.4 pour Microsoft (R) .NET Framework version 1.1.4322.573
dos>dir
12/03/2004 18:24 4 096 impots.dll
12/03/2004 18:20 1 483 impots.vb
12/03/2004 18:26 6 144 test.exe
12/03/2004 18:21 2 805 test.vb
dos>test
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :x x x
syntaxe : marié nbEnfants salaire
marié : o pour marié, n pour non marié
nbEnfants : nombre d'enfants
salaire : salaire annuel en F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 200000
impôt=22504 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :





