Las clases son una estructura de datos bastante flexible. Permiten definir la información y el comportamiento de cualquier unidad que desees modelar en los programas.
Las clases son parte de un paradígma llamado "Programación Orientada a Objetos" (Object-oriented programming) (P.O.O u O.O.P). La cual se focaliza en construir bloques reusables de código llamados Clases. Cuando se necesita utilizar una clase se debe crear un objeto (o instancia), por eso el término de orientación a objetos. Para entender mejor, se debe conocer la terminología común.
-
Una clase es un bloque de código que define atributos (propiedades) y comportamientos necesarios (métodos) para modelar adecuadamente un elemento del programa. Se puede modelar algo del mundo real como una pelota o una guitarra o algo de un mundo virtual como un personaje de un videojuego y sus leyes físicas.
-
Un atributo (o propiedad) es una pieza de información. Es técnicamente una variable que es parte de una clase.
-
Un comportamiento es una acción definida dentro de una clase. Son comunmente conocidos como métodos, los cuales son las funciones definidoas para la clase.
-
Un objeto es una instancia específica de una clase. Tiene valores definidos para los atributos (variables) de la clase. Se pueden crear tantos objetos de una misma clase, como sea necesario.
El operador is
tiene dos funciones. La primera es permitir la herencia simple entre clases
y la segunda permite comparar si un objeto pertenece a una metaclase específica.
El siguiente juego de Piedra, Papel o Tijeras es implementado mediante una herencia simple. Se utiliza el patrón de diseño de double dispatch, para simplificar su lógica.
// Piedra, Papel o Tijeras
// Usando el Patron de Double Dispatch
class Elemento {
pierdeCon(elemento) {}
leGanaAPiedra(){}
leGanaAPapel(){}
leGanaATijera(){}
}
class Piedra is Elemento {
construct crear() {}
pierdeCon(elemento) {
return elemento.leGanaAPiedra()
}
leGanaAPiedra() {
return false
}
leGanaAPapel() {
return false
}
leGanaATijera() {
return true
}
}
class Papel is Elemento {
construct crear() {}
pierdeCon(elemento) {
return elemento.leGanaAPapel()
}
leGanaAPiedra() {
return true
}
leGanaAPapel() {
return false
}
leGanaATijera() {
return false
}
}
class Tijera is Elemento {
construct crear() {}
pierdeCon(elemento) {
return elemento.leGanaATijera()
}
leGanaAPiedra() {
return false
}
leGanaAPapel() {
return true
}
leGanaATijera() {
return false
}
}
var piedra = Piedra.crear()
var papel = Papel.crear()
var tijera = Tijera.crear()
System.print(piedra.pierdeCon(papel)) // true (verdadero)
System.print(tijera.pierdeCon(piedra)) // true (verdadero)
System.print(piedra.pierdeCon(tijera)) // false (falso)
System.print(papel.pierdeCon(tijera)) // true (verdadero)
System.print(papel.pierdeCon(piedra)) // false (falso)
Ahora utilizando las clases de Piedra, Papel o Tijeras vamos a comparar los elementos
utilizando el operador is
.
var piedra = Piedra.crear()
System.print(piedra is Piedra) // true (verdadero)
System.print(piedra == Piedra) // false (falso)
System.print(piedra is Tijera) // false (falso)
System.print(piedra == Tijera) // false (falso)
System.print(piedra is Elemento) // true (verdadero)
System.print(piedra == Elemento) // false (falso)
System.print(Piedra is Elemento) // false (falso)
System.print(Piedra == Elemento) // false (falso)
System.print(Piedra.supertype is Elemento) // false (falso)
System.print(Piedra.supertype == Elemento) // true (verdadero)
Analizando los resultados verdaderos:
-
(piedra is Piedra)
: Verdadero puesto que el objeto piedra es una instancia de la clase Piedra. -
(piedra is Elemento)
: Verdadero puesto que el objeto piedra es una instancia de la clase Piedra y ésta a su vez hereda de Elemento. -
(Piedra.supertype == Elemento)
: Verdadero puesto que el super tipo de Piedra es la misma referencia a la clase Elemento.
Analizando los resultados falsos:
-
(piedra == Piedra)
: Falso puesto que el objeto piedra no tiene la misma referencia que la clase Piedra. -
(piedra is Tijera)
: Falso puesto que el objeto piedra no es una instancia de la clase Tijera. -
(piedra == Tijera)
: Falso puesto que el objeto piedra no tiene la misma referencia que la clase Tijera. -
(piedra == Elemento)
: Falso puesto que el objeto piedra no tiene la misma referencia que la clase Elemento. -
(Piedra is Elemento)
: Falso puesto que la clase Piedra no es una instancia de la clase Elemento. -
(Piedra == Elemento)
: Falso puesto que la clase Piedra no es una referencia a la clase Elemento. -
(Piedra.supertype is Elemento)
: Falso pues que el super tipo de Piedra no hereda de Elemento (ya que el super tipo de Piedra es Elemento).
Podemos sobrecargar el operador is
en nuestras clases y "ocultar" su real naturaleza.
Normalmente podría ser usado para reemplazar las clases básicas (String, Num, Bool, Fn, Fiber, Map, List, Null, entre otras)
y permitir su extensión. Recordemos que en Wren no se puede heredar desde estas clases debido al problema de Reentrancia (Wren no tiene esta capacidad).
Usarlo de esta forma no es común y solo se ha puesto a modo de ejemplo.
class Original {}
class Extendida {
construct nueva() {}
is(otra) {otra == Original}
type {Original}
static supertype {Original}
}
var instancia = Extendida.nueva()
System.print(instancia is Original) // true (verdadero)
System.print(instancia is Extendida) // false (falso)
System.print(instancia.type) // Original
System.print(Extendida.supertype) // Original
Las clases en Wren pueden ser adornadas con atributos especiales. Estos permiten añadir información para dar contexto o almacenar datos que pueden ser de utilidad para herramientas de programación y otras utilidades.
Solamente pueden estar dentro de una declaración de clase o de método.
#atributo = "valor"
class MiClase {
#atributo = "valor"
metodo {true}
}
Los valores pueden ser String, Num, Bool o el tipo especial group. que permite agrupar distintos atributos en un marco en común.
#!cantidad = 10
#grupo(
nombre = "Camilo"
)
class MiClase {
}