Conversión de Tipos: Casting

Como siempre digo, la Programación Orientada a Objetos (OOP, en sus siglas en inglés) nos proporciona una tremenda potencia y flexibilidad cuando se utiliza correctamente en el diseño de nuestras soluciones; y la gran ventaja es que en el lenguaje Xojo encontramos todas las herramientas necesarias para aplicarla de forma que nuestro código base resulte ampliable, cuestión fundamental para proyectos que han de evolucionar, flexibles y reutilizables, contribuyendo de este modo a reducir los tiempos de desarrollo al basarnos en la reutilización de componentes (clases, módulos…) ya probados. Pues bien, una de estas herramientas, común incluso en el uso de lenguajes de programación de más bajo nivel, es la conversión de tipos o Casting de tipos. A continuación te cuento de qué se trata y también por qué la vas a encontrar interesante, muy probablemente.

Si ya estás utilizando Xojo o cualquier otro lenguaje de programación, entonces es prácticamente seguro que ya estés aplicando la conversión de tipos en tus proyectos. Este puede ser el caso a la hora de trabajar con tipos sencillos, como en la asignación de un valor real (coma flotante) a una variable de tipo entero, o bien cuando realizas asignaciones entre algunas de las clases de tipos básicos contempladas por el framework, como ocurre al asignar a una variable de tipo String el contenido correspondiente a otra de tipo Text. Por ejemplo, estos serían algunos ejemplos de conversión de tipos que haces a diario:

Dim n as Double = 10.20
Dim i as Integer
i = n // La variable i contendrá el valor 10, perdiendo precisión como resultado de la conversión implícita del tipo
Dim t as Text = "Hola mundo"
dim s as String
s = t // La variable s pasa a contener el mismo valor almacenado en t. Cuando usemos esta variable sólo podremos utilizar métodos y acceder a las propiedades asociadas con la clase String

En ambos casos se trata de conversiones de tipo implícitas; es decir, estas ocurren sin más gracias a que son gestionadas internamente. Sin embargo en otros casos las conversiones de tipo han de ser explícitas, cuando por ejemplo nos interesa tratar un objeto como si fuera una instancia creada a partir de su padre en la jerarquía, y posteriormente deseamos volver a tratar el mismo objeto como la clase a la que realmente pertenece (clase hija en la jerarquía), de modo que podamos continuar utilizando los métodos, propiedades y eventos definidas en la misma y no en la clase padre.

¿Por qué querríamos hacer algo así? Por muchos motivos. Por ejemplo, en las siguientes situaciones:

  • Cuando se trata de objetos creados mediante clases absolutamente diferentes entre sí, no conectadas por la jerarquía de clases, pero que comparten una o más Interfaces de clase.
  • Cuando se trata de objetos creados a partir de varias subclases que tienen una misma clase padre común.

En el primero de estos casos, probablemente pueda darse el caso de que deseemos mantener una colección de clase común a todos los objetos definida mediante el tipo correspondiente a la Interfaz de Clase. De este modo, absolutamente todos los objetos, sin importar cuál sea su clase de origen, tendrían cabida en dicha colección y podríamos recorrerlos y enviar al conjunto cualquiera de los métodos definidos por la interfaz de clase.

Algo similar es lo que ocurre en el segundo de los casos, facilitando el acceso a las propiedades y métodos comunes definidos por la clase padre y facilitando así el uso conjunto de cualquiera de las instancias que hereden un comportamiento común.

Sin ir más lejos, probablemente en el uso diario de Xojo ya estés utilizando el segundo de los casos cada vez que recorres todos los controles (clase padre Control) utilizados en el diseño de una ventana, página web o vista (en cada plataforma aplica la correspondiente versión de clase padre); facilitando así la posibilidad de escribir fragmentos de código como el siguiente (me hace referencia a la instancia Window sobre la cual se estaría incluyendo el código):

For n as integer = 0 to me.ControlCount - 1
StrBox me.control(n).name
Next

Mostrando de este modo el nombre de todos los controles de la ventana, independientemente de que sean TextField, ListBox, PushButton, etc. Después de todo, todos ellos derivan de la Control. Por tanto, el anterior fragmento también podría haberse escrito de la siguiente forma:

Dim c as Control
For n as integer = 0 to me.ControlCount - 1
c = me.control(n)
MsgBox c.name
Next

Ahora bien, ¿y si sólo quisiéramos mostrar el nombre de los controles que fuesen de una subclase específica? Por ejemplo, ¿y si sólo quisiéramos mostrar el nombre de los TextField? Toda instancia guarda internamente información acerca de su tipo, y que podemos emplear en tiempo de ejecución mediante los mecanismos proporcionados por la introspección, o bien de una forma más ligera mediante el uso del operador IsA si sólo deseamos realizar una comprobación de tipo. Dicho operador nos permite comparar una instancia contra una Clase o Interfaz de Clase, arrojando un valor booleano como resultado de la operación, tal y como cabría esperar.

Por tanto, podríamos mostrar sólo los controles que fuesen TextField de la siguiente forma:

Dim c as Control
For n as integer = 0 to me.ControlCount - 1
c = me.control(n)
If c IsA TextField Then
  MsgBox c.name
End If
Next

Casting en acción

Cuando tratamos una instancia de clase como si fuese un objeto de la clase de la cual deriva, o bien como miembro de una interfaz de clase que implementa, se denomina Downcasting; algo que podríamos traducir como bajar en la jerarquía de clase. Esto es lo que hemos estado haciendo en los ejemplos de código anteriores, al tratar todos los elementos gráficos (controles) de la interfaz de usuario como si fuesen miembros de la clase Control.

Como puedes observar por ti mismo, cada vez que bajamos por la jerarquía de clase de un objeto, estaremos reduciendo tanto el tipo y número de propiedades a las que tenemos acceso como también la cantidad de métodos y eventos que podremos invocar. En definitiva, limitando su funcionalidad. Esto es algo que querremos asumir de buena gana dadas las ventajas que aporta en la flexibilidad de nuestro código.

Ahora bien, si continuando con el mismo ejemplo quisiéramos recuperar las instancias TextField, no como miembros RectControl sino como propias instancias TextField, para poder acceder a las propiedades o bien ejecutar los métodos definidos por dicha subclase, ¿cómo podríamos hacerlo?

Por ejemplo, pongamos por caso que no sólo queremos mostrar el nombre de dichas instancias (algo que nos viene implementado por la clase Control, de la cual heredan todos los controles), sino que también queremos poner el texto de color rojo; es decir, algo que es específico de los TextField y no implementado en la clase padre Control. Aquí es donde entra en juego el Casting o la capacidad de volver a proyectar una instancia de clase como el tipo que nosotros le indiquemos:

Dim c as Control
dim t as TextField
For n as integer = 0 to me.ControlCount - 1
 c = me.control(n)
 If c IsA TextField Then
   MsgBox c.name
   t = TextField(c) // Casting desde instancia general "Control" hacia subclase específica "TextField"
   t.TextColor = &cFF0000
 End If
Next

Es decir, hemos de incluir entre paréntesis el nombre de la instancia sobre la cual queremos aplicar el Casting, incluyendo ante dichos paréntesis el nombre de la clase que deseamos obtener como resultado.

Has de tener en cuenta que cuando utilizamos el mecanismo de Casting no contaremos con las ayudas sobre comprobación de tipos proporcioandas por el compilador. Esto significa que, por ejemplo, nada nos impediría hacer Casting a una clase TextField de un control que realmente no lo fuera, de modo que si posteriormente intentásemos acceder a sus propiedades o invocar sus métodos obtendríamos fallos en tiempo de ejecución. Es precisamente por ello que antes de realizar una operación de Casting siempre hemos de utilizar los mecanismos de introspección proporcionados por el lenguaje para asegurarnos de que realmente los resultados son los esperados.

Por último, podríamos simplificar el anterior código así:

For n as integer = 0 to me.ControlCount - 1
 If me.control(n) IsA TextField Then
   MsgBox me.control(n).name
   TextField(me.control(n)).TextColor = &cff0000
 End If
Next

Reduciendo probablemente en claridad de lectura (intención) de lo que queremos hacer, pero resultando en un código más compacto por otra parte.

En cualquier caso, a lo largo de este ejemplo hemos visto de qué modo podemos navegar por la jerarquía de clase sobre las instancias, aprovechando de este modo la flexibilidad proporcionada para tratar a todas las instancias como miembros de un mismo conjunto y, posteriormente, mantener su identidad específica sólo en aquellos casos en los que sea necesario.

Un comentario en “Conversión de Tipos: Casting

  1. barberiacapital

    Javier Rodriguez, thanks! And thanks for sharing your great posts every week!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *