.. raw:: html 6. Función orientada a objetos ============================== Por consideraciones de optimización, Berry no consideró los tipos simples como objetos. Estos tipos simples incluyen ``nil``, numéricos, booleanos y cadena. Pero Berry proporciona clases para implementar el mecanismo de objetos. Entre los tipos de datos básicos de Berry, ``list``, ``map`` y ``range`` son objetos de clase. Un objeto es una colección que contiene datos y métodos, donde los datos se componen de algunas variables y los métodos son funciones. El tipo de un objeto se denomina clase y la entidad de un objeto se denomina instancia. 6.1 Clase e instancia --------------------- 6.1.1 Declaración de clase ~~~~~~~~~~~~~~~~~~~~~~~~~~ Para usar una clase, primero debe declararla. La declaración de una clase comienza con la palabra clave ``class``. Las variables miembro y los métodos de la clase deben especificarse en la declaración. Este es un ejemplo de declaración de una clase: .. code:: berry class persona static var mayor = 18 var nombre, edad def init(nombre, edad) self.nombre = name self.edad = edad end def tostring() return 'nombre: '+ str(self.nombre) + ', edad:' + str(self.edad) end def es_adulto() return self.edad >= self.mayor end end Las variables miembro de clase se declaran con la palabra clave ``var``, mientras que los métodos miembro se declaran con la palabra clave ``def``. Actualmente, Berry no admite la inicialización de variables miembro en el momento de la definición, por lo que el constructor debe realizar la inicialización de las variables miembro. Las propiedades de la clase no se pueden modificar después de completar la declaración, por lo que la clase es un objeto de solo lectura. Este diseño es para garantizar que la clase se pueda construir estáticamente en el lenguaje C cuando se implemente el intérprete y se pueda usar la propiedad ``const`` modificada para ahorrar RAM La clase de Berry no admite restricciones de acceso y todas las propiedades de la clase son visibles desde el exterior. En las clases nativas, puede usar algunos trucos para hacer que las propiedades sean invisibles para el código Berry (por lo general, hacer que el nombre del miembro comience con un punto “.”). Puede usar algunas convenciones para restringir el acceso a los miembros de la clase, como la convención de que los atributos que comienzan con un guión bajo son atributos privados. Esta convención no tiene ningún uso a nivel gramatical, pero favorece la estructura lógica del código. Instanciar ~~~~~~~~~~ La clase en sí es solo una descripción abstracta. Tomando los autos como ejemplo, conozco el concepto de autos, y cuando realmente queremos usar autos, necesitamos autos reales. El uso de las clases es similar. No solo usaremos esta descripción abstracta, sino que necesitaremos producir un objeto concreto basado en esta descripción. Este proceso se llama **Instanciación de la clase**, abreviado como instanciación, y el objeto concreto producido por la instanciación se llama **Instancia**. La clase en sí no tiene datos, y la creación de instancias produce una instancia basada en la información descrita por la clase y proporciona datos específicos a la instancia. Método y parámetros ``self`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Los métodos de clase son esencialmente funciones. A diferencia de las funciones ordinarias, los métodos pasan implícitamente un parámetro ``self``, y siempre es el primer parámetro, que almacena una referencia a la instancia actual. Debido a la existencia de parámetros ``self``, el número de parámetros del método será uno más que el número de parámetros definidos en la declaración. Aquí usamos un ejemplo simple para demostrar: .. code:: berry class Test def metodo() return self end end objecto = Test() print(objecto) print(objecto.metodo()) Este ejemplo define una clase ``Test``, que tiene un método ``metodo``, que devuelve su parámetro ``self``. Las dos últimas líneas de la rutina imprimen el valor de la instancia ‘objeto’ de la clase ``Test`` y el valor de retorno del método ‘metodo’ respectivamente. El resultado de ejecución de este ejemplo es: :: Se puede ver que el parámetro ``self`` del método y el nombre de la instancia de uso (``objecto`` en el ejemplo) representan el mismo objeto y ambos son referencias de instancia. Use ``self`` para acceder a los miembros o atributos de la instancia en el método. Métodos sintéticos ~~~~~~~~~~~~~~~~~~ Puede declarar métodos y miembros dinámicos sintéticos usando **Miembros virtuales** como se describe en el Capítulo 8.2. Variables de clase ``static`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Las variables o funciones se pueden declarar ``static``. Las variables estáticas tienen el mismo valor para todas las instancias de la misma clase. Se declaran como ``static a = 1`` o ``static var a = 1``. Las variables estáticas se inicializan justo después de la creación de la clase. Métodos de clase ``static`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Los métodos se pueden declarar ``static``, lo que significa que actúan como una función regular y no toman ``self`` como primer argumento. Dentro de los métodos estáticos, no se declara ninguna variable “auto” implícita. Los métodos estáticos se pueden llamar a través de la clase o a través de una instancia. .. code:: berry class static_demo static def incremento_static(i) return i + 1 end def incremento_instancia(i) return i + 1 end end a = static_demo() static_demo.incremento_static(1) # llamada via clase 2 .. code:: berry a.incremento_static(1) # llamada via instancia static_demo.incremento_instancia(1) | type_error: unsupported operand type(s) for +: 'nil' and 'int' | stack traceback: | stdin:6: in function `increment_instancia` | stdin:1: in function `main` | .. code:: berry a.increment_instancia(1) 2 Constructor y Destructor ~~~~~~~~~~~~~~~~~~~~~~~~ Constructor ^^^^^^^^^^^ El constructor de la clase es el método ``init``. Se llama al constructor cuando se crea una instancia de la clase. Por lo tanto, el constructor generalmente se usa para la inicialización de miembros, por ejemplo: .. code:: berry class Test var a def init() self.a ='esto es una prueba' end end El constructor de este ejemplo inicializa el miembro ``a`` de la clase ``Test`` con la cadena ``'esto es una prueba'``. Si instanciamos la clase, podemos obtener el valor del miembro ``a``: .. code:: berry class Test var a def init() self.a ='esta es una prueba' end end Destructor ^^^^^^^^^^ El destructor de la clase es el método ``deinit``. Se llama al destructor cuando se destruye la instancia. El destructor se usa generalmente para completar algún trabajo de limpieza. Debido a que el mecanismo de recolección de basura libera automáticamente la memoria de los objetos inútiles, no hay necesidad de liberar la memoria en el destructor (y tampoco hay forma de hacerlo en el destructor). En la mayoría de los casos, no hay necesidad de usar un destructor, a menos que cierta clase requiera cierto procesamiento cuando se destruye. Un ejemplo típico es que un objeto de archivo debe cerrar el archivo cuando se destruye. Herencia de clases ------------------ Berry solo admite herencia simple, es decir, una clase solo puede tener una clase base, y la clase base usa el operador ``:`` para declarar: .. code:: berry class Test: Base ... end Aquí la clase ``Test`` hereda de la clase ``Base``. La subclase heredará todos los métodos y propiedades de la clase base y puede anularlos en la subclase. Este mecanismo se llama **Sobrecarga**. En circunstancias normales, solo sobrecargaremos métodos, no propiedades. El mecanismo de herencia de la clase Berry es relativamente simple. Las subclases contendrán referencias a la clase base y los objetos de instancia son similares. Al instanciar una clase con una clase base, en realidad se generan múltiples objetos. Estos objetos se encadenarán de acuerdo con la relación de herencia y, finalmente, obtendremos el objeto de instancia al final de la cadena de herencia. Sobrecarga de método -------------------- La **Sobrecarga** significa que la subclase y la clase base usan el mismo método de nombre, y el método de la subclase anulará el mecanismo del método de la clase base. Para ser precisos, las variables miembro también se pueden sobrecargar, pero esta sobrecarga no tiene sentido. La sobrecarga de métodos se divide en sobrecarga de métodos ordinarios y sobrecarga de operadores. Sobrecarga de método común ~~~~~~~~~~~~~~~~~~~~~~~~~~ Sobrecarga del operador ~~~~~~~~~~~~~~~~~~~~~~~ Puede usar la sobrecarga de operadores de la clase para hacer que la instancia admita la operación del operador integrado. Por ejemplo, para una clase sobrecargada con el operador de suma, podemos usar el operador de suma para realizar operaciones en la instancia. Un operador sobrecargado es un método con un nombre especial, y la forma de función sobrecargada de un operador binario es .. code:: ´def’ operador ´(´ otro ´)´ bloque ´end’ **operador** es un operador binario sobrecargado. El operando izquierdo del operador binario es el objeto ``self`` y el operando derecho es el valor del parámetro **otro**. La forma de función sobrecargada del operador unario es .. code:: ´def’ operador ´()´ bloque ´end’ **operador** es un operador unario sobrecargado. Para distinguirlo del operador de resta, el signo menos unario se escribe como ``-*`` cuando está sobrecargado. Las funciones sobrecargadas del operador deben tener un valor de retorno, porque el valor de retorno ``nil`` predeterminado no suele ser el resultado esperado. Tomemos una clase entera como ejemplo para ilustrar el uso de la sobrecarga de operadores. Primero defina la clase ``integer``: .. code:: berry class integer var value def init(v) self.value = v end def +(other) return integer(self.value + other.value) end def *(other) return integer(self.value * other.value) end def -*() return integer(-self.value) end def tostring(other) return str(self.value) end end La clase ``integer`` sobrecarga los operadores suma, multiplicación y simbólicos, y el método ``tostring`` hace que la instancia use la función ``print`` para generar el resultado. Podemos usar una simple línea de código para probar la función de sobrecarga de operadores de la clase: .. code:: berry integer(1) + integer(2) * -integer(3) # -5 El resultado de esta línea de código es una instancia de ``integer``. El valor del miembro ``value`` de esta instancia es ``-5``, que es el mismo resultado de las mismas cuatro operaciones aritméticas con números enteros. Los operadores lógicos no se pueden sobrecargar directamente. Si necesita una instancia para admitir operaciones lógicas, debe implementar el método ``tobool``. El método no tiene parámetros y el valor devuelto debe ser de tipo booleano. La operación lógica de la instancia en realidad se realiza convirtiendo la instancia en un valor booleano, por lo que la operación lógica de la instancia está completamente en línea con la naturaleza de la operación lógica general. El operador de subíndice no se sobrecarga directamente, pero se implementa mediante los métodos ``item`` y ``setitem``. El método ``item`` se utiliza para la lectura de subíndices, su primer parámetro es el valor del subíndice y el valor de retorno es el resultado de la operación del subíndice; ``setitem`` se utiliza para la escritura de subíndices, y su primer parámetro es el valor del subíndice, el segundo parámetro es el valor que se va a escribir; este método no utiliza el valor de retorno. Al operador sobrecargado se le puede asignar cualquier significado, incluso sin satisfacer las propiedades habituales de los operadores. Dada la versatilidad del código y la dificultad de comprensión, no se recomienda que los usuarios den a los operadores sobrecargados una función alejada del significado general. Sobrecarga del operador de asignación compuesto ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ El operador de asignación compuesto no se puede sobrecargar directamente, pero podemos lograr el propósito de “sobrecargar” el operador de asignación compuesto sobrecargando el operador binario correspondiente al operador de asignación compuesto. Por ejemplo, después de sobrecargar el operador “``+``”, puede usar el operador “``+=``” para instancias de clases relacionadas. Vale la pena señalar que el uso de operaciones de asignación compuestas en la instancia hará que las variables de la instancia vinculada pierdan su referencia a la instancia. .. code:: berry class integer var valor def init(x) self.valor = x end def +(other) return integer(self.valor + other.valor) end end a = integer(4) # a: a += integer(5) # a: print(a.valor) # 9 Después de que se ejecuta la línea 11 de código, la instancia enlazada en la variable ``a`` realmente ha cambiado. Esta línea de código es equivalente a ``a = integer(4) + integer(5)``. Si el operador binario de la sobrecarga de clase no modifica el estado de la instancia, entonces el operador de asignación compuesto correspondiente no modificará ninguna instancia (puede generar nuevas instancias). Instancia --------- Una **Instancia** es un objeto generado después de la instanciación de la clase. Una clase se puede instanciar varias veces para generar diferentes instancias. Las instancias de Berry están referenciadas por la clase a la que pertenecen y los campos de datos correspondientes. Todas las instancias de una clase se referirán a esta clase, pero los campos de datos de estas instancias son independientes entre sí. Objeto de clase base de acceso ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ La función integrada ``super`` se utiliza para acceder a objetos de clase superior. Se puede utilizar en clases o instancias. La magia ocurre cuando llamas a un método de la superclase para que se comporte como intuitivamente crees que lo haría. Por ejemplo, el patrón común para ``init()`` es el siguiente: .. code:: berry def init() # hacer cosas antes de super init super(self).init() # hacer cosas después de super init end Tenga en cuenta que las clases siempre contienen métodos ``init()`` implícitos que no hacen nada, por lo que siempre puede llamar a init desde la superclase incluso si no se declaró ningún método ``init()``. Ejemplo completo: .. code:: berry class A var val def init(val) # super(self).init(val) # esto sería válido pero inútil self.val = val end def tostring() return "val=" + str(self.val) end end class B: A var magia # verdadero si el valor es 42 def init(val) super(self).init(val) # llamar a superinit self.magia = (val == 42) end def tostring() if self.magia return "magia!" else return super(self).tostring() end end end ####### Ejemplo de uso > b1 = B(1) > b1 val=1 > b42 = B(42) > b42 magia! **Características avanzadas**: Al llamar a ``super(self). ( )`` ocurre algo de magia. Cuando se llama al supermétodo, los argumentos ``self`` se refieren a la clase específica más baja. Sin embargo, el ```` no se busca desde la clase de ``self`` (que siempre es la más baja), sino desde la superclase de la clase que contiene el método que se está ejecutando actualmente. Ejemplo: .. code:: berry > class A def init() print("In A::init, self es de tipo", classname(self)) end end > class B:A def init() print("In B::init, self es de tipo", classname(self)) super(self).init() end end > class C:B def init() print("En C::init, self es de tipo", classname(self)) super(self).init() end end > c = C() En C::init, self es de tipo C In B::init, self es de tipo C In A::init, self es de tipo C > Explicación: - llamando a ``C:init()`` en ``instancia`` - en ``C:init()`` ``self`` es ``instancia``, ``super(self).init()`` se refiere a la superclase de ``C`` (método actual), es decir, ``B``, por lo que ``B:init()`` se llama con ``instance`` argumento - en ``B:init()`` ``self`` es ``instancia``, ``super(self).init()`` se refiere a la superclase de ``B`` (método actual), es decir, ``A``, por lo que ``A:init()`` se llama con ``instance`` argumento - en ``A:init()`` ``self`` es ``instancia``, imprimir y devolver Nota: por compatibilidad con versiones anteriores, super puede tomar un segundo argumento ``super(instancia, clase)`` para especificar la clase donde resolver el método. Esta función no debe usarse más, ya que es propensa a errores.