Objetos en Visual Basic

Objetos más consistentes

 

Fecha: 01/Feb/98
por Guillermo "guille" Som
Publicado originalmente en Algoritmo en Noviembre'97


En esta ocasión vamos a continuar nuestra incursión en el mundo de los objetos de Visual Basic. Vimos en el anterior número cómo crear unas clases simples. Una de ellas hacía las veces de una colección del tipo de la clase base.

Ahora vamos a construir una clase-colección algo más consistente y menos propicia a fallos.

Realmente la potencia se logra con el Visual Basic 5 (y superiores). ¿Por qué? Porque en la versión 4 sólo podemos crear clases/colecciones algo menos a prueba de fallos.

En VB5 se puede especificar un método o propiedad como "predeterminado", con lo cual conseguimos lo mismo que en la mayoría de los objetos que estamos acostumbrados a usar. Por ejemplo la propiedad Caption es la predeterminada de un Label, así pues, da lo mismo escribir:

Label1.Caption = "El caption de Label1"

Que esto otro:

Label1 = "El caption de Label1"

Además de esta "pequeñez", el VB5 permite crear métodos ocultos y con propiedades específicas de las colecciones, por ejemplo para que nuestra clase/colección se pueda usar en un bucle con For Each...Tendremos que crear un método de esta forma:

 

 

NewEnum es reconocido por el VB5 y lo usa como el valor a devolver cuando se recorre el contenido de la colección con For Each.

 

¿Qué debe tener una clase/colección?

Por supuesto que no hay nada obligatorio, pero además de ofrecer nuevos métodos, se deberían exponer los métodos habituales de cualquier colección: Add, Count, Item y Remove. Normalmente Item es el método por defecto, así que también debería darse esa característica.

 

Nota
En el VB5, si quieres que uno de los métodos de la clase sea la propiedad predeterminada, selecciona en el menú Herramientas la opción Atributos de Procedimiento, en el cuadro de diálogo deberás seleccionar del combo Nombre, el nombre de la propiedad que quieres poner como predeterminada, pulsa en el botón Avanzados>> y en el combo Id. del procedimiento selecciona (Predeterminado).

 

Si hacemos Item el método o propiedad por defecto de nuestra colección, la podremos usar de estas dos formas:

Usando colección.Item(variable):

 

 

Usando colección(variable)

 

 

Como he dicho antes, se debe permitir recorrer la colección con For Each, por tanto debe implementarse NewEnum, con ello podemos recorrer todos los elementos de la colección haciendo esto:

 

 

La forma de implementar los métodos normales de la colección, sería pasando los mismos parámetros que se le pasan habitualmente, en la siguiente imagen veremos los cuatro métodos habituales de toda colección:

 

 

 

Tanto Add como Item sólo admiten o devuelven objetos del tipo cNombre. Esto está bien y debe ser así ya que se supone que esta colección sólo contendrá elementos de esa clase.

El método Add mostrado, no hace ningún tipo de comprobación, porque el propio VB se encargará de ello, ya que este método sólo acepta elementos del tipo cNombre, dará un error de compilación al intentar asignar cualquier otra cosa que no sea un objeto de la clase cNombre.

El método Item permite crear nuevos elementos sobre la marcha, es decir si el índice especificado no existe, se añade como nuevo elemento a la colección. Esta forma de usarlo es conveniente sólo cuando los elementos de la colección tienen una propiedad que los hace únicos. Usando esa propiedad para acceder a los elementos, nos aseguramos que se creen de forma eficiente. Así también aseguramos que el índice aportado siempre será único.

En caso de que ese índice ya lo tenga otro de los objetos de la colección, no habría problema porque no se añadiría a la colección. Si observas el listado, te darás cuenta que sólo se añade si da error al acceder a él, esto es porque cuando quieres obtener un elemento inexistente de una colección el Visual Basic te avisa, por medio de un error, de que dicho elemento no existe.

 

La ventaja de crear nuestras colecciones

Como ya hemos visto, es la de asegurarnos usar el tipo que queremos que contenga. Pero también es la de poder añadir nuevos métodos. Imagínate que quieres que sólo se puedan añadir elementos cuyo Contenido no esté en la colección, podríamos implementar un método de esta forma:

 

 

Lo habitual es tener también un método que permita añadir nuevos elementos a la colección, pero pasando como parámetros los valores a asignar, no el objeto en sí. En nuestro caso el único parámetro sería una cadena la cual se asignaría al Contenido del nuevo elemento añadido a la colección.

Por supuesto el límite sólo lo pondrá nuestras necesidades y lo que estimemos oportuno.

 

Añadir más propiedades al objeto base

Si necesitamos más propiedades (datos) en nuestra clase base cNombre, no habría ningún problema con los métodos añadidos a la clase colección, (salvo si se implementa el que acepta como parámetros los datos a asignar), porque los métodos de la colección tal y como están planteados los de este ejemplo, sólo usan objetos del tipo adecuado.

 

Comprobaciones en el objeto base

La propiedad que tenemos en el objeto cNombre está declarada como Public. Esta no es la forma más segura de proveer propiedades a nuestros objetos, ya que por error se puede asignar un valor que no es el adecuado. Esto se solventa haciendo que esa variable pública sea realmente una propiedad, para que de esta forma, podamos hacer las comprobaciones que creamos convenientes antes de hacer la asignación.

El código de cNombre, podría ser el siguiente:

 

 

Esta propiedad permitirá cualquier tipo de dato, el pequeño inconveniente es que devuelve un Variant en lugar de una cadena; esto no será ningún inconveniente, al menos en la mayoría de los casos.

La forma de solucionar este tipo de cosas es usar, siempre que sea posible ByVal en los parámetros recibidos por nuestros procedimientos. Cuando un parámetro es ByVal, lo que recibe es un valor, por tanto no producirá un error cuando se espere que ese valor sea, por ejemplo una cadena y se le pase un Variant. Si no tuviera ByVal, seguramente que el señorito VisualB nos diría alguna cosilla para recordarnos que no estamos haciendo bien las cosas.

 

Propiedades de sólo lectura

En algunas ocasiones podemos necesitar que nuestras clases tengan propiedades (datos) que sólo sean de lectura, es decir que no puedan ser modificados. Por ejemplo, si a nuestra clase cNombre, le añadiésemos una propiedad FechaCreacion, y el valor devuelto fuese la fecha y hora en que se creó este elemento.

La forma de asignar el valor sería, por ejemplo a la hora de crear la clase, en Class_Initialize o en cualquiera de los métodos de añadir elementos. Después sólo tendríamos que tener una propiedad GET para que devuelva el valor asignado a la variable privada que contiene la fecha. Al no existir ninguna propiedad LET o SET, no se podrá cambiar el valor, salvo que usemos algún método extra para hacerlo.

 

El VB5 no soporta la herencia, pero la simula muy bien

En los grupos de noticias de vez en cuando sale este tema a debate. Pero hablando sinceramente, Visual Basic no dispone de herencia y seguramente no dispondrá de ella, al menos de la forma en que se suele implementar en C++

Seguramente no será un método purista, pero para la mayoría de los casos, podemos usar las propiedades y métodos de una clase existente simplemente proporcionando una propiedad pública que haga referencia a un objeto de esa clase.

El tema del polimorfismo, es decir que un mismo método se pueda usar en distintos contextos, es algo que el VB5 también sabe "medio" controlar y usará el método adecuado de un objeto u otro cuando se presente la ocasión.

Pero esto no es lo que a mí me ha llamado la atención, sino el hecho de que podemos, por ejemplo, crear una clase que se llame cPersona y que herede los métodos y propiedades (todos) de la clase cNombre. ¿Cómo?, haciendo algo tan simple como esto:

Public Nombre As cNombre

Después podremos usar esta nueva clase y usar la propiedad de cNombre tal que así:

 

 

Si la propiedad Contenido la hemos puesto por defecto en la clase cNombre, entonces no será necesario usarla, (como en el caso de la asignación), aunque siempre queda la posibilidad de usarla, como en el caso de la línea Debug.Print que imprime el nombre de esa persona.

 

Hablando de Polimorfismo...

Seguro que habrás leído algo sobre la implementación de Interfaces en VB, yo también, y salvo por la tan manida reutilización de código y el que haya una consistencia en cuanto a la forma de usar procedimientos con el mismo nombre o con el mismo objetivo final... pienso que tampoco es para echar las campanas al vuelo...

Será que siempre he programado conmigo mismo que no veo tanta innovación en esas nuevas formas de hacer las cosas...

De lo que se trata es que las cosas se hagan con algún tipo de lógica y consistencia, es decir que la forma de usar e implementar los procedimientos, (Subs, Funciones, Propiedades y Eventos), sea la misma, al menos en las ocasiones que deban usarse o al menos que se debieran de usar de la misma forma... para que al final del día, nuestro coco no acabe por echar humo...

Imagínate que quieres implementar un procedimiento genérico a todos los objetos que uses en tu proyecto, digamos una función Nuevo, esta sería una función que crearía un nuevo elemento y nos devolvería el objeto creado.

Si cada uno de los programadores encargados de un proyecto, hiciera su propia implementación, seguramente acabaríamos teniendo una forma diferente de usar esa función... lo mismo hasta nos sorprendemos de que dos hayan hecho lo mismo...

La solución a este posible descontrol: hacer una función genérica y global que hiciera la tarea. Al ser global, se ejecutaría desde cualquier objeto y asunto arreglado...

Pero... esto no debe ser así... Aunque, la verdad sea dicha, es que eso es lo que muchos haríamos...

Cuando trabajamos con objetos, debemos procurar que éstos sean lo más independientes posibles, que no necesiten de ninguna función o variable global. Recuerda que cualquier comunicación con el objeto, se debe hacer por medio de las propiedades y métodos de ese objeto. Si no planteamos de esta forma la creación de objetos, al final acabaremos necesitando esta o aquella función de eso o aquél otro módulo y ... ¡Un verdadero caos!

Volviendo al tema que nos lleva, de lo que se trata es de implementar ciertos procedimientos que sean consistentes entre todos los objetos que los usen.

En el caso de los métodos Add, Count, etc. que son comunes a las colecciones, éstos deben ser usados de la misma forma para todas las colecciones que lo usen, sino acabaríamos como los manuales del Visual Basic: hechos un lío, ya que en cada ejemplo usan el método Add de una forma diferente... ¡A ver si aprenden de lo que pretenden enseñarnos!

 

Usando interfaces para unificar criterios

Con el VB5 tenemos la forma de hacer esto: usando Interfaces. Un interfaz es como una especie de plantilla, pero que sólo es eso, una plantilla, sin contenido alguno. El interface nos dice cómo se debe implementar, (usar o codificar), ese procedimiento, pero no nos obliga a usar un mismo código para todos los objetos. Lo único obligatorio, es que usemos los parámetros que nos muestra esa plantilla, esto realmente no es que sea obligatorio, pero debería serlo.

Una vez que tenemos esa plantilla, cada programador se encargará de que internamente haga lo que tenga que hacer, pero lo que muestra a los demás objetos o la forma en la debemos de usarlo será siempre la misma, o debería serlo... pero si no nos ponemos esas metas... mejor seguir con el método tradicional y acabamos antes.

Cada vez que usemos ese interface en nuestra clase/objeto, será de forma consistente, con lo cual nos evitaremos, entre otras cosas tener que pensar o cambiar la forma de usarlo, además de que le facilitamos al VB el trabajo y lo más importante, aceleramos nuestras aplicaciones, ya que el VB sabrá cómo tratar esos métodos incluidos en el interface.

 

Un poco de ejemplo, por favor

Los ejemplos habituales para esto de los interfaces, es el uso de una interfaz para una clase animal: dinosaurios, pulgas, etc. Pero con los tres gatos que tengo y algún que otro "buitre" de los que siempre andan al acecho... estoy bien surtido de animales...

Así que vamos a implementar un interface para el manejo de colecciones.

El tema es bastante simple, sólo hay que añadir una nueva clase a nuestro proyecto, darle el nombre que queramos, en esto de los Interfaces se suele usar la letra I como inicial del nombre de la clase, por ejemplo podríamos llamarla: Icoleccion.

Aquí tienes el código para la creación de esta clase (interfaz):

 

 

Una vez que tenemos nuestra Interface preparada, ahora sólo nos queda implementarla, para ello debemos usar la instrucción Implements y asunto concluido:

 

 

Ahora sólo nos queda rellenar los procedimientos que queramos usar y a usarlos de forma consistente.

La práctica lo hace todo, así que "implementa", practica y si no te gusta... que te devuelvan el dinero... ;-)

Espero que sigamos viéndonos por estos lares, mientras llega la siguiente ocasión, recibe un cordial saludo.

Nos vemos.

Guillermo


Pulsa aquí para bajarte los listados de ejemplo (objetos2_codigo.zip 4.25 KB)
Pulsa en este link, si quieres bajarte esta página con todos los gráficos (objetos2.zip 77.4 KB)


la Luna del Guille o... el Guille que está en la Luna... tanto monta...