Métodos y clases

Aunque en esta Wiki no hemos entrado en esa cuestión, Genie es un lenguaje orientado a objetos, aunque también es cierto que es posible escribir código sin objetos.

No obstante, en muchos casos, nos interesará utilizar objetos cuando programamos, ya que tienen una serie de beneficios, ya que los objetos pueden contener campos, propiedades, métodos y eventos.

Sin entrar en el fondo, lo que nos interesa ahora es que los objetos son entidades que tienen un determinado estado, comportamiento (método) e identidad. Un objeto es una entidad provista de un conjunto de propiedades o atributos (datos) y de comportamiento o funcionalidad (métodos), los mismos que consecuentemente reaccionan a eventos.

Un objeto es una abstracción de algún hecho o ente del mundo real que tiene atributos que representan sus características o propiedades y métodos que representan su comportamiento o acciones que realizan.

Un objeto contiene toda la información que permite definirlo e identificarlo frente a otros objetos pertenecientes a otras clases e incluso frente a objetos de una misma clase, al poder tener valores bien diferenciados en sus atributos.

A su vez, los objetos disponen de mecanismos de interacción llamados métodos, que favorecen la comunicación entre ellos. Esta comunicación favorece a su vez el cambio de estado en los propios objetos.

En definitiva, en la programación orientada a objetos, y Genie es un lenguaje orientado a objetos, se entiende la clase como definiciones de las propiedades y comportamiento de un tipo de objeto concreto. La instanciación es la lectura de estas definiciones y la creación de un objeto a partir de ellas.

Pues eso, que los objetos pertenecen a una clase, y cuando una clase es instanciada se llama al objeto y permite definir nuevos tipos de datos.

Métodos

Los métodos (también conocidos como funciones) son acciones sobre un objeto.

def saludo(gente: string) :string
	return "Hola " + gente
 
init	
	amigo: string = "Pepe"
	print saludo (amigo)
 
	vecino: string = "Felipe"
	print saludo (vecino)

Los métodos siempre comienzan con la palabra clave def, el nombre del método, una lista de parámetros de método (si los hay) y, por último, si devuelve un valor, el tipo de datos de ese valor.

def suma(x:int) :int  // método o función
	x += 1
	return x      // devuelve x
 
init
	z:int = 3
	y:int = suma(z)  // llama al método con el parámetro z
	print ("%d", y)

En el código anterior se define un método llamado 'suma' que toma un parámetro tipo int y devuelve un valor también tipo int.

Como en el ejemplo, los parámetros se colocan entre paréntesis. Puede no haber parámetros definidos y entonces no se escribe nada entre los paréntesis. En caso de más de un parámetro, éstos se separan entre comas.

Alcance de las variables en los métodos

En relación al alcance de las variables, tema que ya vimos, podemos apuntar que en el código anterior la única variable que el método suma puede 'ver' es x. E igualmente, para el bloque principal la x no es accesible (si la llamamos desde el bloque principal, el compilador avisará del error: The name `x' does not exist in the context of `main').

Podemos ver más claramente la cuestión del alcance de las variables en este ejemplo:

def suma(x:string) : string
	x = x + "def"
	return x
 
init
	z:string = "abc"
	y:string = suma(z)
	print("%s %s",y,z)

Al intentar compilarlo ¡¡salta un error!! El compilador entiende que z no es visible dentro de la función, por lo que prohíbe la operación. Una solución:

def suma(x:string) : string
	s:string = x + "def"
	return s
 
init
	z:string = "abc"
	y:string = suma(z)
	print("%s %s",y,z)

Modificadores de parámetros

Ante el problema anterior relativo al alcance de las variables, hay otra solución más directa:

def suma(ref x:string)
	x = x + "def"
 
init
	z:string = "abc"
	suma(ref z)
	print("%s",z)

Vamos a intentar explicarlo. En el código anterior que producía error, se ha pasado a la función el valor de la variable z, pero como no es visible no se puede modificar y salta el error.

En el segundo código (con ref) en lugar de pasarle a la función el valor de z (que no es visible para ella), le pasamos su dirección (o referencia) y así hacemos que sea posible acceder a ella y mofidicarla.

Para conseguir esto Genie tiene un modificador llamado ref que se usa para forzar que pase la dirección de una variable y no su valor (ver Tipos de referencia).

Y es que en Genie los parámetros de las funciones tienen un comportamiento predeterminado dependiendo de si se trata de un tipo de valor (que se copia para la función) o un tipo de referencia (que no se copia, simplemente a la función se le pasa una referencia a ellos).

Pero este comportamiento se puede cambiar con los modificadores ref y out usados tanto en la llamada a la función como en sus parámetros. Un ejemplo:

init
	var
		a = 1
		b = 2
		c = 3
 
	// llamada a la función nuevo de Numeros
	Numeros.nuevo (a, out b,  ref c)
	/* a es un tipo de valor que será copiado tal cual, sin cambio,
	 * según el comportamiento predeterminado de los parámetros
	 * 
	 * b es un tipo de valor con el modificador oput: el valor no se copia.
	 * El parámetro se considera no inicializado y
	 * espera que la función lo inicialice
	 * 
	 * c con el modificador ref se trata de un valor de referencia.
	 * El parámetro se considera inicializado y la función puede cambiarlo o no
	 * 
	*/
 
	print "a=%d, b=%d, c=%d", a,b,c
 
class Numeros : Object
	// la función nuevo asiga nuevos valores
	def static nuevo (a:int, out b: int, ref c: int)
		a = 10		
		b = 20	// si comentamos esta línea, b = 0
		c = 30  // si comentamos esta línea, c = 3
 
// El resultado de este código es: a=1, b=20, c=30

Modificadores de métodos

En el código anterior vemos un método en el que a la palabra clave def le sigue la palabra static, se trata de un modificador del método.

Además de los modificadores de los parámetros, en los métodos podemos definir otras opciones incorporando unos modificadores (estos modificadores no son exclusivos de los métodos ya que también pueden usarse en propiedades y eventos).

En los métodos estos modificadores siempre se colocan entre la palabra clave def y el nombre del método. Los modificadores son private, extern, inline y static.

Dentro de los modificadores podemos distinguir los modificadores de acceso que marcan los objetos como privados o públicos. Mientras que public determina que no hay restricciones de acceso (el método se puede llamar desde cualquier fragmento de código), private (valor por defecto si no se especifica lo contrario) limita el acceso al interior de la estructura del método. Indica que este método no es accesible desde fuera y solo es accesible dentro del ámbito del bloque que se identifica en el objeto. No es necesario incluir el modificador private si su nombre comienza con un guión bajo, ya que cualquier objeto que empiece con guión bajo siempre se considera privado.

Otros modificadores hacen referencia al origen donde se define el método. El modificador extern indica que el método está definido fuera del package de Genie / Vala (a menudo es implementado en C). Entonces, si fuera necesario, los archivos C se deben incluir junto al archivo ejecutable compilado y especificarlo al compilador valac.

El modificador inline indica al compilador que el método debe ser tratado en línea para mejorar el rendimiento. Esto es útil para métodos muy pequeños.

El modificador static indica un método que se puede ejecutar sin necesidad de una instancia de clase (y como consecuencia, los métodos estáticos nunca cambian el estado o las propiedades de una instancia de clase).

Clases

Los métodos o funciones que acabamos de ver pueden estar incluidos en una clase. Pero además de funciones, las clases pueden contener propiedades (entre otras cosas, como los eventos que veremos cuando tratemos la interfaz Gtk).

Las propiedades describen características variables de una clase. Las definimos con prop:

prop color:string = "verde"
prop altura: int = 44

Si los métodos definen funcionalidades de las clases, las propiedades definen sus características.

init
	var perro_1 = new Perro()
 
	print perro_1.color
	print perro_1.raza
 
class Perro:Object
 
	prop color:string = "blanco"
	prop raza:string = "caniche"

Un ejemplo básico de una clase con un método:

// creamos la clase
class EjemploClase : Object 
 
	// incorporamos un método a la clase
	def run () 
		stdout.printf ("Hola mundo, soy el método de una clase.\n")
 
// punto de entrada de la aplicación
init
	// instanciamos la clase asignando una variable
	var ejemplo = new EjemploClase ()
	// llamamos al método run
	ejemplo.run ()
	// vuelve al bloque principal desde el método invocado
	print "Adios."

Un ejemplo de una clase con métodos y propiedades:

init
	// creamos mi_tirada en la clase Ruleta
	var mi_tirada = new Ruleta()
 
	// llamamos a los métodos de la clase
	print "%s",mi_tirada.apuestas()
	mi_tirada.jugar()
	stdout.printf ("%s",mi_tirada.apuestas())
 
	// consultamos los valores de las propiedades de la clase
	print ("Gana: %d, %s, %s.", mi_tirada.numero, mi_tirada.color, mi_tirada.par)	
 
class Ruleta:Object		// creamos la clase Ruleta
 
	// definimos propiedades variables de la clase
	prop numero:int = 0         // del 1 al 36
	prop color:string = "rojo"  // rojo o negro	
	prop par:string = "impar"   // par o impar
 
	// introducimos un método que define una funcionalidad
	// este método define el valor de las propiedades
	def jugar()			
		numero = Random.int_range (1, 37)	// un número del 1 al 36	
		if numero % 2 == 0
			par = "par"			
		col:int = Random.int_range (0, 2)			
			if col == 0
				color = "negro"			
 
	// introducimos un método que define otra funcionalidad
	// este método informa del valor de una propiedad
	def apuestas ():string
		var ap = ""
		if numero == 0
		   ap = "Cierren sus apuestas.\n"
		else
		   ap = "No se admiten más apuestas.\n"
		return ap

Por cierto, en el código anterior también vemos como obtener un número aleatorio de un rango de números con Random.int_range().

En relación a los objetos y sus métodos, también hay que considerar los bloques constructores (construct).

Como hemos visto, una clase puede tener muchos métodos con nombres y parámetros diferentes. Un bloque de construcción se utiliza para definir uno de esos métodos, que puede ser instanciado a través de la instancia de la clase seguido de un punto más el nombre del constructor con sus correspondientes parámetros entre paréntesis. Con construct recogemos variables o parámetros del objeto cuando lo instanciamos con new y llamamos a una función.

Un construct funciona igual que init y se ejecuta cuando instanciamos el objeto con new, pero sin embargo init siempre se dispara antes que construct.

init	
	//var coche = new Coche( 120 )  // instancia a la clase con un parámetro	
	var coche_diesel = new Coche.diesel( 100 )  // instancia al constructor de la clase con un parámetro
 
class Coche:Object
 
	construct( b:int )
		vel = b
		print "El coche con gasolina corre a %d km/h", vel
 
	construct diesel( c:int )   
		vel = c
		print "El coche con diesel corre a %d km/h", vel
 
	prop vel:int   // una propiedad de la clase
 
	init					
		print "La carrera ha empezado."   // este bloque init marca el inicio de la secuencia de ejecución	
 
	final
		print "La carrera ha terminado."  // este bloque es lo último en ejecutarse

El bloque init sirve para incluir el código de inicialización de la clase, pero no tienen parámetros y solo puede haber uno en una clase. Después de ejecutarse el bloque init, la secuencia de ejecución sigue el construct llamado y solo después termina con el contenido del bloque final. En el ejemplo puedes probar a comentar y descomentar las líneas iniciales de instancia de clase (var coche = … y var coche_diesel = …) para comprobar la secuencia de ejecución del código en cada caso.

Un bloque final (conocidos como destructors en contraposición a los constructors) se utiliza para realizar la finalización de la clase.

🔝