Hola a todos. Gracias por ver esta presentación. Voy a hablar sobre algunas clases de JMP y cómo pueden trabajar con ellas en JSL para implementar comportamientos de interfaz dinámicos y extensibles.
Me llamo Ben Barrett y trabajo en IDEXX Laboratories. Soy ingeniero allí desde hace unos diez años. He trabajado con diversas aplicaciones, como LabVIEW y MATLAB, y también he experimentado con C#. En general, en todos estos lenguajes, siempre me he inclinado por la programación orientada a objetos.
Por alguna razón, eso me resulta muy intuitivo. Sin embargo, al empezar a trabajar con objetos en JSL, descubrí que esas intuiciones no me resultaban muy útiles. Tuve que superar algunos matices, pero una vez que aprendí a usarlos, los encontré muy prácticos.
Voy a empezar con una breve introducción. No voy a dar una lección exhaustiva sobre las clases de JMP. Simplemente quiero compartir mi proceso de descubrimiento al empezar a trabajar con ellas. Algunas de las cosas que me resultaron difíciles, los problemas que encontré y cómo los solucioné, y al final algunos ejemplos interesantes de cómo las he utilizado.
Aquí tienen un programa provisional; veremos si lo sigo al pie de la letra. Quiero hablar un poco sobre el ámbito de las variables, ya que he descubierto que ha sido un problema recurrente en la implementación de algunas clases, y esto me llevará a compartir algunas de las mejores prácticas que he encontrado. También quiero comentar que las clases en JSL difieren un poco de lo que cabría esperar de otros lenguajes como C#. Después, presentaré un ejemplo de código funcional.
No esperes una lección profunda y exhaustiva sobre clases en JSL, ni siquiera sobre los principios del diseño orientado a objetos en general. Esperemos que esto te ayude a evitar algunos quebraderos de cabeza si decides profundizar en las clases por tu cuenta.
Aquí está la agenda, ya la repasé toda. Aquí tienen un breve resumen de los distintos ámbitos disponibles en JSL. No pretendo comprender del todo cómo interactúan ni cómo se pueden usar. Tengo algunas ideas básicas al respecto.
Existen cuatro ámbitos generales: el ámbito local, el global y los espacios de nombres personalizados. En este documento explico cómo interactuar con cada uno de estos ámbitos. Recomiendo consultar la documentación en línea para obtener más información. En general, al trabajar con clases, el mejor método que he encontrado es utilizar espacios de nombres.
Crea tus propios espacios de nombres personalizados si necesitas referenciarlos en clases; esto me ha ayudado a evitar muchos problemas de ámbito y conflictos. También he descubierto que el espacio de nombres `Here` no es muy fiable en el caso de las clases, así que intento evitar usar nombres con el valor predeterminado `Here` y asignarle el valor `1`. Además, usar variables locales también puede ser útil, siempre que evites el comando `local Here`. Sin embargo, esto requiere un poco más de mantenimiento, ya que tienes que definir explícitamente qué variables quieres que sean locales. Todo esto quedará más claro en las demostraciones.
Acabo de repasar las mejores prácticas. Evita usar nombres predeterminados, usa espacios de nombres personalizados y también clases. Puedes pensar en las clases como un espacio de nombres que se puede copiar, y puedes tener duplicados en memoria simultáneamente. ¿En qué se diferencian las clases JSL de las clases que ya conoces? Seguro que hay más características que algunos esperarían ver en las clases, pero creo que estas cinco representan una descripción bastante estándar de lo que se espera de los objetos.
Encapsulación, herencia, polimorfismo, replicación y abstracción. La herencia, el polimorfismo y la abstracción están interrelacionados, y las clases en JSL ofrecen un buen soporte para la encapsulación, la herencia y la replicación. Sin embargo, existen ciertas particularidades en la implementación del comportamiento polimórfico y la abstracción. A continuación, hablaré brevemente sobre estos cinco conceptos.
La encapsulación está bastante bien soportada. Al definir una clase, se definen las propiedades y los métodos asociados a ella. El único matiz es que ninguna de estas afirmaciones es del todo cierta. No se puede definir un miembro verdaderamente privado de la clase. Todo es público. Es como un espacio de nombres. Existe acceso global a los miembros y propiedades de una clase.
La herencia también es compatible. Si crea una clase derivada de otra, esta heredará los miembros de la clase padre. Existen algunas particularidades en torno al polimorfismo, que analizaremos con más detalle posteriormente. Para implementar el polimorfismo de forma efectiva, se requiere una solución específica que permita a la clase padre delegar correctamente los métodos de la clase hija desde sus propios métodos.
La replicación es, básicamente, la razón principal para usar clases. Puedes instanciar múltiples copias de un objeto, y cada una tendrá su propio conjunto encapsulado de parámetros y métodos, y la verdad es que no he tenido ningún problema con esto.
En cuanto a la abstracción, en JSL no existe un concepto explícito de abstracción, pero se puede definir una clase padre con métodos predeterminados que, por ejemplo, devuelvan un error o realicen alguna acción básica. Si conoces C# u otros lenguajes, esto se asemejará más a un método virtual que a un método abstracto. Esta es una breve descripción de cómo las clases en JSL pueden diferir de las clases que ya conoces si vienes de otros lenguajes. Con esta rápida introducción a las clases en JSL, paso a otro tema.
Tenemos abierta nuestra instancia de JMP. Tengo un par de ejemplos bastante sencillos. Bueno, un ejemplo sencillo con el que quiero empezar, luego uno un poco más complejo y, finalmente, uno mucho, mucho más complejo. El primero del que quiero hablar es una barra de progreso muy básica.
Estas demostraciones tienen como objetivo hacer que el desarrollo de interfaces gráficas de usuario (GUI) sea más extensible y modular, permitiéndote aprovechar al máximo las capacidades de los objetos. En esta barra de progreso, la ejecutaré rápidamente para que veas su funcionamiento. Encontré la implementación básica de una barra de progreso en línea, en una publicación donde alguien preguntaba al respecto. Te mostraré los detalles de cómo se crea la barra de progreso, pero no son lo más importante para este ejemplo.
Este código básicamente configura algunos elementos para definir el aspecto de la barra de progreso. Crea una ventana. Crea… Como pueden ver, aquí creamos los objetos, y enseguida veremos el código de esos objetos. Luego, simplemente organiza los elementos en la ventana, añadiendo algunos datos que… representan la tarea que se está ejecutando. Después, itera como si estuviera realizando algún procesamiento y actualiza las barras de progreso mediante estos métodos, que forman parte de la definición de la clase.
Aquí no hay ningún giro, pero este objeto encapsula todo el comportamiento que crea esta visualización. Voy a abrir esa ventana. Ahora estamos en el código. Voy a la definición de la clase de la barra de progreso y vamos a minimizar esto rápidamente. Como pueden ver, se define una clase usando la llamada a `define class`, se le da un nombre y así se instancia. Definir variables en una clase es tan sencillo como… Se definen de la misma manera que cualquier otra variable.
Ahora, este valor se asigna a la instancia de la clase, por lo que se accede a él mediante la referencia a la clase seguida de su nombre. Los métodos en las clases se definen de la misma manera que se define una función. En esencia, son funciones con un nombre diferente, y se asignan al objeto de la clase. Como mencioné antes, las clases funcionan como un espacio de nombres, lo que permite manipularlas. Una vez creada una instancia de una clase, se pueden manipular todos estos aspectos. Se pueden agregar propiedades que se asignarán al objeto creado. Se pueden realizar todo tipo de operaciones dinámicas con las clases de JMP.
No voy a entrar en muchos detalles. Solo quiero centrarme en cómo se crea esta barra de progreso. La función de inicialización se ejecutará. Veamos esto desde otra perspectiva. En este ejemplo, esta función de inicialización se ejecutará al crear el objeto. La llamada al nuevo objeto ejecutará automáticamente `_init`.
Al inicializar, tenemos una configuración predeterminada, pero también puedes usar la que quieras. Esto configura los aspectos básicos y, al final, ejecuta el comando de compilación. El método de compilación se define más abajo. En este método, creamos todos los objetos que dan forma a la barra de progreso. Tenemos el contenedor y algunos espaciadores, que se expanden y contraen para crear el efecto de progreso. Esto proviene de una publicación de la comunidad de JMP; no tengo el código fuente a mano.
Esto simplemente configura todos los detalles de cómo se verá la barra de progreso. Aquí tenemos algunos métodos auxiliares que permiten modificar la altura o el ancho, y se pueden usar para cambiar el progreso. También está la cuestión de cómo interactuar con ella y modificar el avance. Estos son solo métodos con diferentes funcionalidades; por ejemplo, `set` establece el porcentaje explícitamente a un valor. `step` la avanza una cantidad determinada, y luego se pueden definir otras opciones.
Podrías indicarle exactamente cuántos pasos hay y luego avanzar en cada iteración. Podrías manipular los métodos según las necesidades de la tarea específica. De nuevo, si retomamos el ejemplo ahora que tenemos el contexto, es bastante sencillo. Simplemente creamos el objeto y luego… Al configurar la nueva ventana, para aprovechar el objeto, debemos poder obtener el elemento de visualización.
En la barra de progreso, el contenedor principal es este cuadro de lista horizontal. Pero si obtenemos el objeto, simplemente devolvemos ese valor o ese contenedor. En la definición de la nueva ventana, si queremos insertar la barra de progreso en una ubicación específica, podemos simplemente tomar el objeto o el objeto que creamos y obtenerlo… Esto podría aclararse como algo similar a obtener el panel. Eso tendría más sentido, pero así estamos.
En fin, esa sería la forma de insertarlo. Aquí seguimos haciendo referencia al objeto y lo recorremos paso a paso para la barra principal, asignándole un valor explícito a la barra secundaria. Actualizamos el texto. Repasémoslo una vez más para ver qué sucede. De nuevo, solo asignamos valores aleatorios, lo actualizamos, y puedes imaginar cómo podrías insertarlo en otra tarea.
Otra cosa que quiero mencionar, y que me ha resultado útil, es que puedes cerrar la barra de progreso. Si lo haces, la próxima vez que intente iterar, fallará porque ya no tendrá la referencia a la ventana. La has cerrado, y ahora, al ejecutar esta actualización para intentar actualizar la ventana, no podrá hacerlo. Por lo tanto, devuelve un error.
Como todo esto está dentro de un bloque try, si falla, la llamada se redirige aquí. Esta es una forma rápida y sencilla de interrumpir el procesamiento si es necesario, algo que me ha sido útil en varias ocasiones. Esa es la barra de progreso. Una implementación bastante básica. Se puede personalizar de muchas maneras: para obtener información, para lo que sea.
Lo siguiente que quiero explicar es algo más complejo y probablemente requiera un poco de preparación. Quiero mostrarles estas tablas. He tomado la tabla de muestra de la clase grande y le he añadido datos aleatorios para demostrar la funcionalidad de la unión virtual. Para quienes no estén familiarizados con las uniones virtuales, existe la unión estándar donde podríamos... Tenemos esta clase de puntuaciones altas. Tiene varias entradas para cada estudiante. Intentaron realizar una tarea 10 veces y obtuvieron una puntuación. Solo contiene su nombre, el intento y la puntuación, pero tenemos toda esta otra información aquí. Queremos incluirla aquí. Realizamos una unión. Estoy seguro de que cualquiera que use JMP ya lo ha hecho antes.
Eso incorporará estos valores a esta tabla, pero hay otra forma de hacerlo. Todos estos nombres son únicos. Podemos configurarlos como un ID de enlace y, luego, aquí en esta tabla, podemos usarlo para hacer referencia a esta tabla. Ahora tenemos una unión virtual y podemos ver toda la información. Si volvemos aquí, puedo ocultar o mostrar la información, y ahora tenemos acceso a la información de esta columna de esta tabla sin necesidad de realizar una unión. Podríamos hacer lo mismo con esta tabla.
Esto tiene algunas ventajas muy interesantes. Si accedemos a la información de la columna y a la propiedad de referencia de vínculo, podemos configurarla para que acepte el estado seleccionado o excluido. Si aplico esta configuración y luego selecciono esta fila, se seleccionarán todas las filas relacionadas. Esto también se aplica al crear un resumen y vincularlo a la tabla o vista de datos; ocurre algo similar.
Esta es una función muy útil. Empecé a usarla el año pasado y enseguida quise automatizarla con JSL, así que terminé creando un gestor de enlaces. Permítanme borrar esto. Borraremos el ID del enlace. Borraremos la referencia del enlace. Ahora son tablas normales sin enlaces.
Voy a abrir el gestor de enlaces de ejemplo. Voy a ejecutar algunas de sus funciones. Vamos a configurar todas las tablas, inicializar el gestor de enlaces y enseguida les mostraré el código subyacente.
¡Listo! No sé cuál fue el error, pero aquí tenemos la ventana básica del administrador de enlaces. Ya tenemos la interfaz gráfica, pero aún no se ha enlazado nada. Con la API que podemos usar, podemos establecer una fuente de enlace, lo cual es básicamente equivalente a lo que hice antes, donde configuré el ID del enlace. Si lo ejecuto, veremos que el administrador de enlaces se ha actualizado.
Aquí aparece esta nueva entrada en la interfaz gráfica. Ahora podemos vincular una de las tablas. Aparece una nueva entrada. Podemos vincular la segunda tabla. Aparece una segunda entrada y ahora tenemos la posibilidad de acceder y modificar estas configuraciones de vinculación.
Si quiero que se envíe esta tabla, es decir, si quiero la segunda tabla, puedo configurarla para que la selección y las exclusiones sean todas... Básicamente, si selecciono algo aquí, puedo hacer que se propague a allá. Hemos seleccionado lo que sea. Ahora, todas estas tablas deben estar seleccionadas y, además, en la tabla original, también debe estar seleccionada la tabla de origen.
Normalmente, para actualizar estos datos, habría que acceder, hacer clic derecho, seleccionar "Información de columna", ir a "Referencia de vínculo", luego a "Enviar" o "Aceptar", modificar lo que se desee y aplicar los cambios. Luego, repetir el proceso en la otra columna. Buscaba algo más dinámico, algo que permitiera visualizar el proceso. Se podría tener otra tabla, múltiples fuentes configuradas para gestionar todo esto, y todas las configuraciones estarían aquí mismo, permitiendo desactivar elementos, eliminar vínculos, etc.
Esta herramienta me resultó especialmente útil al trabajar con grandes conjuntos de datos distribuidos en múltiples tablas, donde se puede tener una tabla resumen principal, se ha procesado información continua y se desea conectar los datos, pero sin crear una tabla gigantesca que resulte poco manejable. De ahí surgió esta herramienta.
Ahora bien, si queremos adentrarnos en el código, la cosa se complica un poco, y quiero dejar muy claro desde el principio que nada de esto está optimizado. Gran parte de esto es aún... podríamos decir que es la primera versión preliminar. Logré que funcionara, pero no he revisado ni optimizado nada. Ténganlo en cuenta mientras explico algunos de estos puntos.
Aquí puedes ver la función de inicialización que llamé; esta simplemente comprueba si existe un objeto y, de no ser así, lo crea. Una de las cosas que probablemente se podría mejorar es que, inicialmente, pensé que podría necesitar varios gestores de enlaces activos simultáneamente si tengo varios proyectos abiertos, y tal vez sea útil, tal vez no, pero esta clase está implementada esencialmente como un singleton por el momento.
Podrías instanciar más si quisieras, pero esa funcionalidad sería un poco problemática en ciertos detalles que explicaré en un momento. Básicamente, el administrador de enlaces se encarga de mantener arrays que definen cómo se enlazan las tablas, qué tabla está enlazada con cuál y cuál es el estado actual del enlace. Incluye métodos para modificar los enlaces y realizar otras operaciones.
Para la interfaz gráfica, hemos implementado dos clases. Está la configuración de enlaces. Si puedo abrir la ventana del administrador de enlaces... ¿Dónde está? Aquí mismo.
Si observamos la ventana del administrador de enlaces, la configuración de enlaces es el cuadro que permite modificar la aceptación de envíos y seleccionar exclusiones. Esta sería la configuración, y luego la pestaña de origen de enlace. Un origen tiene su propia pestaña porque existen reglas sobre qué columnas se pueden vincular y qué elementos se pueden vincular a ellas, entre otras cosas. Al crear un origen de enlace, el administrador de enlaces crea uno de estos objetos.
Hay un montón de otras cosas que se ejecutan para comprobar cosas y configurar eventos para saber cuándo se cierran las tablas, etc., pero puedes ignorar todo eso. Esto es lo esencial: crea un nuevo objeto de pestaña de origen de enlace y le proporciona la tabla.
Eso es todo. Se añade a la interfaz gráfica de usuario (GUI) y creo que está por aquí abajo. Aquí está la lista de fuentes, y esto se añade a ella, que es solo un contenedor. Aquí tenemos la función para obtener la pestaña, que es el objeto de visualización. Esto gestiona gran parte de la replicación. Cada vez que creamos una nueva fuente, creamos una nueva instancia de este contenedor y obtenemos automáticamente toda la funcionalidad.
Si vamos a la pestaña de origen, también veremos una inicialización. Tiene su propio objeto de visualización (un cuadro de página), se realiza una alineación y también tiene un objeto de lista. Aquí se encuentran todas las configuraciones de enlaces posibles. Recuerda que probablemente no esté del todo optimizado, pero se puede apreciar que es un poco más fácil implementar comportamientos dinámicos en una interfaz de JMP. Lo he utilizado en otros lugares.
Algunos quizás hayan notado que tengo aquí arriba esta biblioteca de recuperación; a eso me refería hace un rato cuando empecé a hablar de una implementación mucho más compleja. No creo tener mucho tiempo para profundizar en esto. Ojalá pudiera, porque este fue el primer proyecto que emprendí con JMP Classes. Desafortunadamente, también está aún en fase de borrador. Hay muchas cosas aquí que podrían consolidarse fácilmente.
Supongo que, llegados a este punto, lo que más espero es haberles hecho reflexionar sobre cómo aprovechar las clases en JMP para lograr un comportamiento más dinámico y un código más limpio. Porque sé que cuando intentaba hacerlo… vi código de interfaz de usuario complejo y dinámico donde todo se integraba directamente en la función de nueva ventana.
En la nueva ventana, terminas con 20 pestañas llenas de funciones y botones que hacen esto y aquello, cuando existen formas mucho más limpias y orientadas a objetos de implementar el mismo comportamiento. Es más fácil de mantener, escalar y leer. Creo que trabajar con clases ofrece muchas ventajas al implementar interfaces de usuario.
No voy a entrar en detalles sobre la recuperación de datos. Lo único que añadiré es que trabajaré para limpiar y publicar todo este código de ejemplo y la biblioteca de recuperación en un repositorio Git público, donde cualquier persona interesada podrá consultarlo, clonarlo, bifurcarlo, usarlo como desee e intentar comprender cómo aprovechar las clases JMP en su propio código para aumentar su eficiencia.
Creo que lo único que queda es repasar brevemente algunos de los puntos iniciales que comenté sobre el alcance. Ya hemos visto los ejemplos y cómo empezar a construir estructuras para implementar estas interfaces gráficas dinámicas, pero ahora toca entender cuáles son las dificultades y cómo solucionarlas. Un buen ejemplo sería… Veamos si podemos hacerlo aquí.
Si elijo una de estas, el problema de ámbito del que hablé antes se relaciona con cómo se llaman los métodos y el ámbito del que heredan. He visto varios problemas al intentar trabajar en el espacio de nombres `Here`. He visto colisiones entre el espacio de nombres global y los métodos, y, para simplificarlo al máximo, la forma infalible que he encontrado para evitar cualquier problema de ámbito que puedas encontrar con una clase (aunque no lo voy a demostrar aquí, este es un buen ejemplo de cómo podrías hacerlo) es simplemente usar un modificador local. Me gusta formatearlo así.
La única variable que necesita definirse explícitamente es esta. Aquí, se trata simplemente de una variable que estamos configurando para analizar y crear la propiedad que generará la referencia. Ese código es irrelevante. Esta variable, por sí sola, podría ocultar una variable en otro ámbito, simplemente porque no se definió como local, y dependiendo de dónde se llame a este método en el código, podría generar un conflicto.
Esta es la forma más limpia que he encontrado para evitar conflictos de ámbito con las clases: ser muy explícito con las variables locales. Supongo que tendré que hacerlo en prácticamente todos estos métodos, ya que la mayor parte del código se escribió antes de comprender cuál era la mejor manera de solucionar algunos de estos problemas. La única otra solución que ayuda a evitarlos es usar la identificación explícita del espacio de nombres.
Cada vez que escribo una biblioteca, esencialmente la escribo en un nuevo espacio de nombres personalizado, e intento definir todas mis variables y métodos, o variables y funciones, como parte de ese espacio de nombres también, y eso me ha ayudado a evitar conflictos.
Entonces, supongo que otro tema que podríamos abordar, aunque este no sea el mejor ejemplo, serían algunos de los problemas relacionados con el polimorfismo y la herencia. Veamos. Un buen lugar para hablar de ello podría ser la biblioteca de recuperación.
Con suerte, no será demasiado complicado… Podemos obviar algunos detalles de la implementación y centrarnos únicamente en los problemas. Según lo que hemos visto hasta ahora, la descripción general es que este panel de recuperación es esencialmente un contenedor que se puede configurar para recuperar automáticamente los valores de los campos. Ahora bien, está diseñado para ser heredado, por lo que se espera que tenga clases hijas que necesiten invocar parte de su comportamiento. Concretamente, este método. La clase hija tendría que implementar su propia versión del método `build`.
Aquí es donde entra en juego el matiz del polimorfismo, porque si intentas llamar a este método desde otro de estos métodos (estamos en la clase padre y queremos llamar a `build`, pero hay una clase hija que lo sobreescribe), no se enviará correctamente la llamada a `build` al método de la clase hija. No sé por qué, pero creo que debe haber algún matiz en cómo se implementan las clases en el backend. Sin embargo, existe una forma de sortearlo. Creo que se ilustra mejor en otra biblioteca, pero lamentablemente, no estoy preparado para explicarlo.
Creo que voy a evitar entrar en detalles sobre eso y simplemente intentaré decir que acabas de concluir diciendo que el polimorfismo, o el hecho de que puedas implementar una clase hija que anule el comportamiento, puede que no funcione como se espera, lo cual es un poco desafortunado porque creo que la herencia es uno de los aspectos más útiles de las clases.
Si bien es posible heredar comportamientos, resulta un poco más complicado invocarlos en la clase padre. Creo que así lo explicaré. Cuando publique este material, intentaré incluir ejemplos más específicos sobre cómo y por qué esto no funciona. En resumen, solo pretendo ofrecer una visión general de cómo empezar a incorporar clases en los diseños de interfaz de usuario en Junk JSL.
Dicho esto, no me extenderé más. Repito, me llamo Ben Barrett y les animo a contactarme si tienen preguntas o desean ver más ejemplos de estas implementaciones. Muchas gracias.