Xojo: Interfaces de clase

La programación orientada a objetos nos permite realizar abstracciones de forma que nuestro código sea sumamente flexible. Así, tal y como sucede en el mundo real, mediante la categorización a través de las clases podemos definir una serie de variables de instancia (propiedades) y acciones de instancia (métodos) comunes para los objetos creados a partir de dichas clases.

El objeto de todo esto es que posteriormente nuestros objetos puedan comunicarse entre sí, y ser enviados como argumentos en la invocación de otros métodos. Luego, mediante la creación de diferentes subclases, es posible afinar la especialización mediante la definición de nuevas propiedades y métodos específicos para una tarea determinada.

Todo esto es mucho más sencillo verlo a través de un ejemplo, como pueda ser algo tan socorrido como la propia clasificación del reino animal.

Parte de la jerarquía del reino animal

Así, en lo alto de nuestra jerarquía estaría la clase base ‘Animales’ en la que se incluirían todas las propiedades y acciones (métodos) aplicables a cualquier tipo de animal; piensa en ello como la clase base Object utilizada por el framework de Xojo. Es decir, sobre la que pueden derivar otra serie de clases especializadas.

De hecho, continuando con nuestro ejemplo, la siguiente capa de especialización sería la generada mediante dos subclases: “Mamíferos” y “Aves”; de modo que en cada caso se volverán a incluir propiedades y acciones que serán específicos para los objetos creados a partir de ambos grupos, y por tanto también heredados por las subclases generadas a partir de dichos grupos. (Puedes encontrar un buen ejemplo de esto en la clase RectControl  correspondiente al framework de Xojo).

Esto es lo que nos lleva al siguiente nivel de especialización que puedes ver en la figura. Se trataría en este caso de las clases encargadas de definir el estado y comportamiento típicos de cada uno de los animales derivados de Animales -> Mamíferos y Animales -> Aves. Está claro que si bien todos los animales comparten una serie de atributos (color de ojos, número de patas, orejas, etc.), y acciones (beber, comer, dormir,), a medida que se desciende en la cadena se va añadiendo especialización hasta que, en el caso de un perro, obtendremos una serie de características y acciones que no son iguales a los de, por ejemplo, una vaca (ambos en la rama Animales->Mamíferos); y mucho menos a los de un loro (en este caso en la rama Animales->Aves).

¿Y por qué incidir tanto en la jerarquía de clases a estas alturas? Sencillo, sigue leyendo.

Un objeto puede ser varios objetos a la vez… en la misma jerarquía

Ya sabemos que mediante la declaración (creación) de nuevas clases lo que estamos realizando entre muchas otras cosas es crear nuevos tipos a utilizar en nuestra aplicación de Xojo. Por tanto, como tales, podremos definir nuevas variables que no sólo utilicen cualquiera de los valores primitivos (números enteros, booleanos o números de coma flotante, por ejemplo), sino también variables que sean referencias de los tipos definidos por nosotros.

Por tanto, más allá de las variables locales (las utilizadas como cuerpo de nuestro código, dentro de los métodos) o variable globales, definidas como tales en los objetos como los módulos, ventanas o nuestras propias clases, también podremos pasar como argumentos en las invocaciones de los métodos los tipos así definidos por nosotros. Por tanto, bajo la siguiente definición de método:

    miMetodo(p as Perro)

Podremos pasar como argumento cualquier variable que apunte a un objeto de tipo ‘Perro’, pero no a una variable que apunte a un objeto ‘Mamifero’ o un objeto ‘Animal’. ¿Por qué? Como probablemente recuerdes de cuando estuvimos tratando las clases, un objeto del tipo ‘Perro’ **es un** ‘Mamifero’ y también **es un** ‘Animal’, pero un objeto de tipo ‘Animal’ o un ‘Mamifero’ **no es** necesariamente un Perro: no tiene las propiedades ni los métodos de su especialización.

Fallo en la correspondencia de Tipos

Haz la prueba tú mismo. A estas alturas no deberías de tener muchos problemas para crear los esqueletos de cada clase, así como el método de prueba en el evento ‘Open’ de la ventana Window1 (también puedes utilizar la imagen como guía).

Como verás, cuando intentas pasar un objeto de tipo ‘Mamifero’ a un método definido para aceptar sólo objetos de tipo ‘Perro’, el compilador arroja un error de correspondencia de tipo a la hora de compilar la aplicación. Algo sin duda bueno que nos evitará muchos problemas en tiempo de ejecución.

¿Cómo solucionar este problema? Parece bastante sencillo, ¿no? Todo lo que hemos de hacer es cambiar el tipo pasado como parámetro en el método de ‘Perro’ a ‘Animal’ de este modo podremos pasar tanto instancias de objeto de la clase más genérica como de cada una de las especializaciones que cuelguen de su jerarquía. Así, cambiando el método a:

    vacunaAnimal(a as Animal)

También podremos utilizar el siguiente código de forma segura:

      dim a as new Animal

      dim m as new Mamifero

      dim p as new Perro

      vacunaAnimal(a)

      vacunaAnimal(m)

      vacunaAnimal(p)

Dado que tanto las instancias ‘p’ del objeto Perro, ‘m’ de la clase Mamífero y ‘m’ de la clase Animal son Animales, el método aceptará todos ellos.

Ahora bien, aquí hay un riesgo subyacente. Imaginemos que en el método ‘vacunaAnimal’ nos encontramos con las siguientes sentencias:

    if vacunaOK = true then

        a.ladra

    end if

¿Qué crees que ocurrirá?

Método no definido en clase

Estamos intentando ejecutar un método de una clase específica (‘Perro’) sobre una referencia a un objeto de una clase superior (‘Animal’)… que no tiene definido dicho método. Por tanto, el Compilador mostrará un error indicando que dicho método no existe.

Por tanto, está claro que en nuestros métodos hemos de utilizar con cuidado la declaración de tipos en la definición de las variables, propiedades y métodos ¡en función de cuáles sean los métodos que deseamos aplicar en cada caso! (hay otras formas de ver qué objeto en concreto se oculta sobre la clase más general, pero eso no lo veremos por el momento y tampoco es muy elegante desde el punto de vista de la programación orientada a objetos). 

El mejor resumen que se puede hacer en este sentido, en cualquier caso, es el siguiente: si tus métodos van a trabajar sobre objetos de una clase, limítate a invocar métodos definidos en dicha clase y sus respectivas superclases, así como acceder a las propiedades (públicas), declaradas hacia arriba en la jerarquía del árbol.

Interfaces: tratar con perros y canarios

Ahora bien, imaginemos por un momento que en nuestro programa de ejemplo queremos discernir entre cualesquiera de los objetos que desciendan de ‘Animales’, independientemente de si estos son ‘Mamiferos’ o ‘Aves’, y que cumplan con la condición de que sean ‘Mascotas’ de modo que pueda invocar sobre ellos acciones como ‘acariciar’, ‘Mimar’ o ‘Pasear’.

De acuerdo, es posible que puedas considera animales como los Leones o Águilas animales de compañía (de hecho conozco a gente que trata a sus leones como sus mascotas, pero personalmente procuro mantenerme alejado a una distancia más que prudente). En nuestro ejemplo nos ceñiremos a lo que cualquiera podría considerar mascotas: perro, gato, loro o canario, tal y como muestra la imagen que continua con nuestro ejemplo:

¿Cómo utilizar métodos que sean aplicables a objetos de dos ramas en una jerarquía de clases?

Sabiendo como sabemos que Xojo no permite que una subclase pueda descender simultáneamente de dos clases, ¿cómo podemos invocar métodos que estén implementados por los objetos correspondientes a dos ramas en una jerarquía de clases?

Probablemente tu primer impulso sea el de implementar el método en una clase superior, como ‘Animal’, de modo que este sea heredado por cualquier miembro de la subclase. En este caso, no se soluciona el problema, ya que seguiríamos sin poder limitar la invocación del método a un objeto que fuese simplemente una ‘Mascota’, y podríamos acabar acariciando a un León (poco aconsejable, a no ser que lo hayas criado tu mismo).

Quizá tu segundo impulso sea el de acudir a la sobrecarga de métodos, tal y como vimos en entregas anteriores, de modo que puedas implementar un código ‘más dócil’ sobre aquellos animales peligrosos en los que puedas invocar indebidamente el método ‘acariciar’. Tampoco es la solución. Se trata más bien de un parche que, además, te obligará a pensar en todas las excepciones que se puedan producir cada vez que quieras añadir una nueva acción sobre tus mascotas.

La respuesta se encuentra en las interfaces de clase. A diferencia de lo que podrías esperar de otros lenguajes como ‘C’ o ‘C++’, entre otros, cuando en Xojo hablamos de la interfaz de clase no nos referimos a la parte del archivo encargado de declarar la interfaz pública de una clase.

En Xojo, las interfaces de clase son una importante herramienta, similares a la creación de las clases, con la única excepción de que es de tipo declarativo. Es decir, cuando creamos una interface de clase simplemente nos limitamos a escribir la signatura de los métodos, con la enorme ventaja de que nuestras clases pueden implementar tantas Interfaces de clase como deseemos.

Posteriormente, cuando declaramos que una clase implementa una interface, estamos obligados a incluir todos los métodos definidos por dicha interface, así como el código que se ejecutará cuando se invoque cada uno de dichos métodos.

Una interface de clase permite añadir acciones comunes a miembros procedentes de diferentes clases, incluso si estas parten de diferentes clase base

¿Y sabes qué es lo mejor de todo? Que una interface de clase permite incorporar acciones (métodos) comunes a los miembros procedentes de diferentes clases, incluso si estas no tienen en común una misma clase base. Esto significa que, en nuestro ejemplo, no sólo podemos dotar de las acciones ‘acariciar’, ‘mimar’ o ‘pasear’ a los miembros de las clases ‘Perro’ y ‘Gato’ descendientes de ‘Mamíferos’, y ‘Loro’ y ‘Canario’, descendientes de ‘Aves’ (ambas con la clase base común ‘Animales’); sino que también podríamos convertir en nuestra mascota al miembro de la clase ‘Tamagochi’, procedente de la clase raíz ‘Juguetes’ y que no comparte una clase base común con ‘Animales’. Sin embargo, sí que tiene todo el sentido del mundo ‘acariciar’, ‘pasear’ o ‘mimar’ a nuestros Tamagochi, ¿verdad? (bueno, quizá tanto como hacerlo con un León, pero seguro que coges la idea.) 

Definir y aplicar Interfaces de Clase

¿Y como puedes crear Interfaces de Clase? Bien sencillo, sólo has de seleccionar Insert > Class Interface en la barra de menús del IDE de Xojo. A continuación, en el panel Inspector, asigna el nombre que desees dar a tu interfaz de clase (‘Mascotas’ en nuestro ejemplo). Por ahora, olvídate del botón asociado a ‘Agregates’.

Inspector: Definir Interface de Clase

A continuación, selecciona tu recién creada Interface de clase en el Navegador de Proyecto, y añade la definición de todos los métodos necesarios, tal y como harías si se tratase de una clase o un módulo.

Nuestra jerarquía de clases, y definición de Interface de clase completadas

El segundo pasos consiste en asignar la interface de clase recién creada sobre los objetos en los que quieras invocar dichos métodos; de modo que posteriormente puedas referirte a ellos como ‘Mascotas’, además de por su clase original y cualquiera que suba por la jerarquía, tal y como vimos anteriormente.

Seleccionar y asignar Interfaces de Clase sobre una Clase

Para ello, selecciona la clase en cuestión y, en el panel Inspector, pulsa sobre el botón Interfaces. Se abrirá un diálogo en el que, de entrada, verás todas las Interfaces de clase que ya están definidas por el propio framework de Xojo, lo que significa que puedes implementarlas en tus propias clases. Sin embargo, lo que ahora nos interesa es que ubiques tu recién creada interface de clase ‘Mascotas’, marques la casilla de verificación asociada con dicha entrada y, por último, confirmes la selección (se cerrará la ventana).

Métodos de interface de clase añadidos automáticamente

Como resultado, verás en el Navegador de Proyecto que ahora la clase en cuestión ha ganado automáticamente los métodos declarados por la interface de clase en cuestión y, de hecho, si seleccionas cualquiera de ellos observarás que Xojo te indica en el Editor de Código a qué interface de clase se corresponde. Sólo te queda añadir el código propiamente dicho que se ejecutará cada vez que se invoque el método en cuestión sobre cualquier instancia creada a partir de la clase.

Invocando los métodos de nuestras Interfaces de clase

Veamos si todo esto funciona. Vuelve a redefinir el método de la ventana ‘Window1’ de modo que su signatura sea ahora la siguiente:

    acariciaMascota(m as Mascotas)

Y cambia el código correspondiente al Evento ‘Open’ de Window1 para que sea el siguiente:

      dim a as new Animal

      dim m as new Mamifero

      dim l as new Leon

      dim av as new Aves

      dim ag as new Aguila

      acariciaMascota(a)

      acariciaMascota(m)

      acariciaMascota(l)

      acariciaMascota(av)

      acariciaMascota(ag)

¿Qué crees que pasará? Si has seguido el texto con atención hasta este punto, deberías de ser capaz de saberlo sin que tus ojos se vayan hacia la imagen inferior.

Error de correspondenciaDeTipo

Exacto, ¡ninguna de las clases de esas instancias son ‘Mascotas’! Prueba ahora con lo siguiente:

      dim p as new Perro

      dim g as new Gato

      dim l as new Loro

      dim c as new Canario

      dim t as new Tamagochi      

      acariciaMascota(p)

      acariciaMascota(g)

      acariciaMascota(l)

      acariciaMascota(c)

      acariciaMascota(t)

Como verás, la aplicación compila y se ejecuta a la perfección. No importa cuál sea la clase de cada instancia, ¡y tampoco importa si comparten una misma clase base! Ahí tienes al ‘Tamagochi’ para demostrarlo. Lo que importa es que todas ellas declaran ser ‘Mascotas’ y, por tanto, existe concordancia de tipo y es seguro invocar el método ‘acariciaMascota’, declarado por la interface de clase en cuestión.

En resumen, las interfaces de clase suponen el mejor modo de que nuestras clases sean muchas clases a la vez, flexibilizando hasta extremos nuestro código. Eso sí, como en todas las cosas, hay que utilizarlas con criterio.

¿Te ha gustado lo que has leído en esta entrega? Pues está esperando mucho más, porque puedes comprar con un 40% de descuento el libro que estoy escribiendo sobre el lenguaje de programación Xojo, y en el que se abordan más en profundidad los temas tratados en cada una de estas entregas. Puedes comprarlo como pedido anticipado aquí

Enlaces de Interés sobre Xojo

Artículos anteriores

Javier Rodríguez (@bloguintosh) es desarrollador OS X e iOS, director de Macsoluciones.com. Puedes contactar con él para el desarrollo de aplicaciones para Mac e iOS en entornos empresariales así como consultoría y formación.

Javier Rodríguez

Javier Rodríguez (@bloguintosh) es desarrollador y autor del eBook "Programación Multiplataforma Xojo". Puedes contactar con él para el desarrollo de aplicaciones y soluciones multiplataforma, así como consultoría y formación en www.aprendexojo.com

0 0 votos
Article Rating
Subscribe
Notify of
0 Comments
Opiniones Inline
Ver todos los comentarios
0
Me encantaría saber tu opinión, por favor, deja un comentariox
()
x