Como ya sabes Xojo es un potente lenguaje orientado a objetos, pero como muchos, muchos otros lenguajes de programación también necesita utilizar los tipos de datos más básicos o también llamados “primitivos”, como por ejemplos números enteros o de coma flotante. Pero cuando se trata de utilizar valores numéricos nos encontramos con algunas situaciones en las que podría mejorarse el asunto. Por ejemplo, ¿cómo sabes si una propiedad de tipo Integer se ha definido con un valor? Después de todo, todos toman el valor 0 por omisión y este es un valor válido en cualquier contexto en el que necesitemos utilizar dicha propiedad. Sería útil ser capaces de saber en nuestro código si efectivamente un valor ha sido definido o cuenta con su valor por omisión; o bien poder sumar un valor con todos los disponibles en una matriz (Array) de Integer utilizando para ello una sintaxis legible, y también de un modo más encapsulado. Continúa leyendo para ver una técnica orientada a objetos que puedes utilizar, así como para aprender a sobrecargar algunos operadores.
Ya sea por diversión o para aprender algo nuevo, vamos a crear una clase Number que te permitirá saber si realmente se ha definido un valor, además de “resetearlo” o incluso activarlo/desactivarlo de modo que su valor se tenga en cuenta o no a la hora de utilizarlo en otras operaciones, como por ejemplo cuando deba sumarse con otro número de tipo entero o de coma flotante… y también veremos como poder añadir una instancia de la clase Number a los valores de un Array.
Por supuesto, aquí veremos el esqueleto de dicha clase (funcionalidad limitada), pero estoy convencido de que serás capaz de ampliarla y modificarla para que se ajuste a tus necesidades y propósitos.
Aspectos básicos de la clase Number
Comencemos con el esqueleto realmente básico de la clase Number, así que añade una nueva Clase a tu proyecto Xojo y nómbrala como Number en el Panel Inspector.
El valor de la clase Number estará almacenado realmente en una propiedad primitiva de tipo Double (coma flotante); añade por tanto una nueva Propiedad a la clase Number y usa el Panel Inspector con los siguientes valores:
- Name: Value
- Type: Double
- Scope: Protected
También queremos saber si el valor se ha definido en cualquiera de las instancias de la clase, de modo que añade una nueva propiedad a la clase utilizando los siguientes valores en el Panel Inspector:
- Name: IsSet
- Type: Boolean
- Scope: Protected
Y por supuesto también queremos tener la capacidad de activar o desactivar una instancia cualquiera de la clase Number, de modo que su valor asignado pueda (o no) ser tomando en cuenta a la hora de utilizarse en operaciones matemáticas. Una vez más, añade la tercera propiedad requerida por nuestra clase Number:
- Name: Enabled
- Type: Boolean
- Scope: Public
El siguiente paso consiste en añadir algunos método Constructor a nuestra clase, de modo que podamos añadir nuevas instancias que cubran algunos casos comunes:
- Crear una nueva instancia sin ningún valor definido: la propiedad IsSet estará a False y la propiedad Enabled a True.
- Crear una nueva instancia con el valor Entero o de coma flotante proporcionado: la propiedad IsSet estará a True y la propiedad Enabled también a True.
- Crear una nueva instancia con el valor numérico proporcionado como String: IsSet estará definido a True y Enabled también a True.
El Constructor por defecto de la clase será el que no recibe ningún parámetro. De modo que añade un nuevo método a la clase Number utilizando los siguientes valores en el Panel Inspector:
- Method Name: Constructor
- Scope: Public
Y teclea la siguiente línea de código en el Editor de Código asociado:
self.Enabled = true
Añade un segundo método a la clase y utiliza los siguientes valores en el Panel Inspector (este Constructor creará una nueva instancia a partir del valor Entero o de coma flotante recibido):
- Method Name: Constructor
- Parameters: Value As Double
- Scope: Public
Y escribe las siguientes líneas de código en el Editor de Código asociado:
Self.Constructor Self.value = value IsSet = True
Añade un tercer Constructor; este será el que reciba una String como parámetro. Añade el tercer método a la clase utilizando los siguientes valores en el Panel Inspector:
- Method Name: Constructor
- Parameters: Value As String
- Scope: Public
Y escribe las siguientes líneas de código en el Editor de Código asociado:
Self.Constructor Self.Value = Double.FromString(value, locale.Raw) Self.IsSet = True
Pero… ¡espera! No sería genial si pudiésemos crear nuevas instancias de la clase Number a partir de otra instancia de Number recibida como parámetro? ¡Por supuesto! Así que añade un nuevo método de nuevo utilizando los siguientes valores en el Panel Inspector:
- Method Name: Constructor
- Parameters: Value As Number
- Scope: Public
Y escribe las siguientes líneas de código en el Editor de Código asociado:
Self.Constructor If value.IsSet And value.Enabled Then Self.Value = value.Value Self.IsSet = True End If
Por ahora nuestra clase tiene cuatro métodos especiales, todos ellos con el mismo nombre: Constructor. Si está definido en cualquiera de tus clases, este es el método que se llamará/ejecutará cada vez que utilizas la palabra clave New en combinación con el tipo de dato que representa el nombre de la clase. Por ejemplo:
Var n As New Number // Se llama al Constructor por omisión. Var i As New Number(10) // Llama al Constructor que toma un Entero o valor de coma flotante como parámetro. var s As New Number("120.20") // Llama al Constructor que toma un String como parámetro. Var objNumber As New Number(n) // Llama al Constructor que toma otra instancia Number como parámetro.
Cuando se añaden varios métodos con el mismo nombre a una clase pero en los que se utilizan diferente número o tipo de parámetros así como de tipo devuelto se denomina Sobrecarga de Método (Method Overloading, en inglés). Lo encontrarás en cualquier lenguaje de programación orientado a objetos. De modo que nuestro método Constructor está sobrecargado para cubrir todos los casos definidos.
Pero, como has visto en los anteriores ejemplos, no hay nada realmente nuevo o elegante. Quiero decir, crear una nueva instancia a partir del valor recibido en el método Constructor está bien, pero, ¿no sería mejor y más elegante si pudiésemos hacer simplemente algo más natural como lo siguiente?:
Var i As Number = 10 var s As Number = "120.20" var objNumber = n
Sobrecargar el Operador de Asignación
¡La buena noticia es que podemos hacerlo! El lenguaje de programación Xojo tiene la capacidad de sobrecargar los operadores, tal y como hicimos con el método Constructor. Y cuando se trata de sobrecargar el operador de asignación necesitamos implementar el método Operator_Convert. Este será el que se ejecute cada vez que nuestra clase reciba un valor de otro tipo de dato o bien sea la propia instancia la que necesite convertirse al tipo de dato esperado. Por ejemplo:
Var i As Number = 10 // El Integer 10 necesita ser convertido a una instancia Number. var intValue As Integer = i // La variable "i" (cuyo tipo de dato es Number) necesita convertirse a un tipo de dato Integer.
La cuestión más importante que has de recordar es que cuando se ejecuta el método Operator_Convert entonces no llegará a ejecutarse el Constructor de la clase; de modo que nuestro código deberá de encargarse de inicializar correctamente la instancia tal y como esperas.
Vamos a implementar el método Operator_Convert que se ejecutará cada vez que una instancia de Number deba ser convertida a un valor de tipo Integer o Double.
Añade un nuevo método a la clase Number utilizando los siguientes valores:
- Method Name: Operator_Convert
- Return Type: Double
- Scope: Public
Y escribe la siguiente línea de código en el Editor de Código asociado:
Return if( self.Enabled, Self.Value, 0 )
Añade un segundo método Operator_Convert, cambiando en esta ocasión el valro devuelto a String, de modo que el valor interno de nuestras instancias Number pueda ser asignado a una variable de tipo String (o bien usado en otras operaciones que involucren Strings), tal y como sería en estos casos:
Var i As Number = 10 Var s As String = i
Utiliza los siguientes valores en el Panel Inspector asociado y teclea la línea de código en el Editor de Código asociado:
- Method Name: Operator_Convert
- Return Type: String
- Scope: Public
Return If( Self.Enabled, Self.Value.ToString( Locale.Raw), "" )
Necesitamos sobrecargar el método Operator_Convert dos veces más, de modo que pueda cubrir la operación de asignación cuando el valor recibido esté a la derecha del operador de asignación en vez de a la izquierda.
Utiliza los siguientes valores en el Panel Inspector para cada uno de los dos nuevos métodos sobrecargados, escribiendo luego las líneas de código disponibles a continuación de la definición en cada uno de sus Editores de Código asociados:
- Method Name: Operator_Convert
- Parameters: Value As Double
- Scope: Public
Self.Enabled = True Self.Value = value Self.IsSet = True
- Method Name: Operator_Convert
- Parameters: Value As String
- Scope: Public
Self.Enabled = True Self.Value = Double.FromString( value, Locale.Raw ) Self.IsSet = True
Sobrecargar el Operador de Suma
Asignar valores a variables o propiedades de tipo Number o bien instancias de Number a propiedades o variables de tipo Integer, Double o String está bien, pero no resulta de gran utilidad por ahora. De modo que el siguiente paso consiste en implementar la capacidad de sumar una instancia Number con otra instancia Number, o con otros tipos de datos Integer, Double o String, e incluso con todos los valores disponibles en un Array. Para todo ello necesitamos sobrecargar el operador “+” (suma) implementando el método Operator_Add en nuestra clase Number.
Esto nos permitirá escribir código como el siguiente:
Var n2 As Number = i + 10
Aquí el valor 10 se convierte en primer lugar a una instancia de tipo Number que, posteriormente, se sumará con la instancia Number de la variable “i” y su resultado asignado a la variable “n2”.
var n3 As Integer = i + 10
Aquí el valor 10 se convierte en primer lugar a una instancia de tipo Number que se suma a la instancia de Number almacenada por la variable “i” y cuyo resultado se convierte a un Integer que se asigna finalmente a la variable “n3”.
De modo que necesitamos implementar el método Operator_Add recibiendo como parámetro un valor de tipo Number y devolviendo también una instancia de tipo Number.
Añade un nuevo método a la clase Number utilizando los siguientes valores en el Panel Inspector, y escribiendo a continuación el código encontrado a continuación en el Editor de Código asociado:
- Method Name: Operator_Add
- Parameters: Value As Number
- Return Type: Number
- Scope: Public
Var no As New Number( If(Self.Enabled, Self.Value, 0) + value.Value ) Return no
Este método funciona bien cuando tenemos una instancia Number a la izquierda del operador de suma, pero no cuando la instancia Number está a la derecha. Para dicho escenario hemos de sobrecargar otro método: Operator_AddRight.
Añade por tanto otro método a la clase Number utilizando los siguientes valores en el Panel Inspector asociado:
- Method Name: Operator_AddRight
- Parameters: Value As Double
- Return Type: Double
- Scope: Public
Y escribe la siguiente línea de código en el Editor de Código asociado:
Return if( self.Enabled, Self.Value, 0 ) + value
Añadir Instancias de Number a un Array de Enteros
También sería realmente útil si pudiésemos añadir una instancia de Number a un array de valores enteros, o incluso una instancia de Number a un array que contenga otras instancias de tipo Number. Por ejemplo, tal y como vemos en el siguiente código:
Var arrayNumbers() As Number Var arrayIntegers() As Integer For z As Integer= 0 To 10 arrayNumbers.add System.Random.InRange(10,200) arrayIntegers.add System.Random.InRange(10,200) Next Var z1 As Number = i + arrayNumbers Var z3 As Integer = i + arrayIntegers
Por supuesto esto implica implementar nuevos métodos sobrecargados tanto para Convert_Add y para Convert_AddRight. Estas son las implementaciones para sobrecargar Convert_Add tomando un array de Double y también recibiendo un Array de instancias Number. Te dejaré como ejercicio la implementación de Convert_AddRight.
- Method Name: Operator_AddRight
- Parameters: Value() As Integer
- Return Type: Double
- Scope: Public
Var sum As Double For Each item As Double In values sum = sum + item Next Return sum + If( Self.Enabled, Self.Value, 0 )
- Method Name: Operator_AddRight
- Parameters: Value() As Number
- Return Type: Number
- Scope: Public
Var sum As Double For Each item As Number In values If item <> Nil And item.Enabled Then sum = sum + item.Value Next Return sum + If( Self.Enabled, Self.Value, 0 )
Resetear Instancias Number
El último paso para nuestra clase Number de ejemplo consiste en implementar el método que se encargará de “resetear” la instancia. Por tanto, añade un nuevo método a la clase Number utilizando los siguientes valores:
- Method Name: Unset
- Scope: Public
Y añade las siguientes líneas de código al Editor de Código asociado:
Self.IsSet = False self.Value = 0
Conclusión
Como has visto, la sobrecarga de métodos es una técnica muy potente y útil tal y como hemos hecho con el método Constructor y también con los métodos Operator_Convert, Operator_Add y Operator_AddRight. Pero esta clase aun precisa de trabajo adicional para cubrir otros escenarios como puedan ser la resta, multiplicación o división, entre otros. La implementación de estas operaciones no deberían de suponer mucho esfuerzo teniendo en cuenta lo que ya hemos visto en este ejemplo con los métodos sobrecargados Operator_Add y Operator_AddRight.