23 ene. 2020

Las Clases y los Objetos creados por ellas PARTE 1

Referencias:
--------------------------------------------------------------------------------------------------------------------
https://carlossanchezperez.wordpress.com/2013/05/19/mi-guia-de-ruby-las-clases-y-curiosidades-oop/
https://medium.com/@gsanchezd/entendiendo-los-objetos-en-ruby-18bcce934d66
https://ru.coursera.org/lecture/aplicaciones-web/video-3-programacion-rubi-i-objetos-clases-y-herencias-7E4Wa
https://www.youtube.com/watch?v=ArnePVp68F0
---------------------------------------------------------------------------------------------------------------------
Los Objetos, tanto en Ruby como en la vida real, están definidos por sus cualidades y variables, que le han sido otorgados desde algún proceso o mecanismo que los generó.

En Ruby, este proceso o mecanismo es la Clase.

Una Clase es una manera de hacer objetos, en ella se definen, por un lado las cualidades de ella misma (variables de clase) necesarias para llegar a hacer las cualidades que van a tener los objetos que de ella salgan (variables de objeto).

Las variables en Ruby pueden ser:

nombre = variable local
@nombre = variable de instancia
@@nombre = variable de clase
$Nombre =variable universal

A.- Los Objetos que salen de las clases:

Todos los objetos que salgan de esa clase tendrán las mismas característica básicas, éstas características son; las variables, los métodos (que son las acciones que pueden hacer) y los procedimientos.

En Ruby a los objetos se les llaman también Instancias y las variables que se definan dentro de la clase se conocen como variables de instancia si están referidas a ella, inicialmente apuntan a nil, se identifican porque empiezan con @ y gracias a ello pueden ser accesibles desde fuera de la clase mediante el procedimiento adecuado según interese, se definen mediante el método de instancia def... end y esto es así siempre.

Para crear un objeto de esa clase, fuera de la misma clase, hay que utilizar el método .new asociado a una variable según la sintaxis:

variable = nombredelaclase.new() --> cuando se definen constructores
ó
variable = nombredelaclase.new --> cuando no se definen constructores

Los objetos se crean con una clase y tiene que:
  • Sus variables empiezan con @
  • Los métodos que los llaman son def... end
  • Se accede a ello mediante nombredelaclase.métododeinstancia
y este objeto creado contendrá los parámetros que en la clase se le asignen.

Así que podemos ver a los objetos como una compilación de variables, métodos y procedimientos emanados desde una clase concreta, pues de esta manera tenemos la visión desde el objeto hacia la clase.

Para crear una Clase, se empieza con la siguiente sintaxis:

class (nombre de la clase con la primera en mayúscula)
.
.
.
end


Un ejemplo, vamos a crear una clase que englobe a todos los animales, que normalmente quedan definidos por un nombre, un género y una especie;

class Animal
end

En esta clase llamada Animal que no hace nada por ahora, pero podemos comprobar que es una clase, que es una clase del tipo Class y que tiene los métodos heredados de Class más los propios de la clase mediante las siguientes líneas:

puts "Está creada la clase #{Animal}"
puts "Es de la clase #{Animal.class}"
puts "Y tiene los métodos siguientes: #{Animal.methods}"

Esto nos dará como salida:

Está creada la clase Animal
Es de la clase Class
Y tiene los métodos siguientes: ........ (Pondrá varias líneas heredadas de la clase Class pues no hay ninguno definido para Animal, por ahora.)

Ahora bien, como la necesidad de crear la clase Animal es porque entre los métodos de Class no existen los de los animales, es que se necesita establecer unos criterios que serán una base común para todos los objetos que de ella salgan, pero como los valores de sus parámetros han de partir de los mismos criterios, habrá de inicializar las variables de instancia cada vez que se quiera crear un objeto para que no interfiera en los demás, y esto se consigue mediante algún procedimiento que lo consiga.

El procedimiento es variado; con los métodos con los accesores.

1.-  Métodos: por un lado podemos ponerlo explícito, con constructor def initialize()... end

class Animal
def initialize (nombre, genero, especie)
@nombre = nombre
@genero = genero
@especie = especie
end
end

animal = Animal.new('Perro', 'Cánido', 'Canis Lupus Familiaris')
puts animal
animal = Animal.new('Gato', 'Felino', 'Felis Silvestris Catus')
puts animal

La salida de esto da:

<Animal:0x0142e57c>
<Animal:0x0142e464>


En este caso, lo que hace es utilizar la orden constructora initialize(nombre, genero, especie) para decir que lo que esté dentro de ese def serán los valores que van a tener las variables de cada instancia que se creen y son obligatorios en la creación del objeto, de lo contrario dará un error por faltar datos para contruir el objeto, por ejemplo

@nombre = nombre
@genero = genero
@especie = especie

Con esto le decimos que la variable nombre, genero y especie se identificarán (porque serán asignadas) con las variables de instancia @nombre, @genero y @especie respectivamente.

La salida que nos da este código, son dos objetos distintos de la clase Animal con un número alfanumérico identificativo distinto 0x0142e57c y 0142e464, respectivamente.

Al ejecutar este código en otro momento nos dará otros números, como es lógico pensar, pero no queda duda de que hace objetos distintos.

Ahora, ¿qué parámetros, características o peculiaridades tienen esos objetos?

Para acceder a esa respuesta habrá que proceder de tal manera que podamos acudir a ellos según nos interese, bien para simplemente poder leerlos, para modificarlos sobrescribiéndolos o para ambas cosas.

Hay varios procedimientos

Como vemos,  a través de un método: podemos acceder a ellos en un método definido para esa clase,
de esta manera los podremos llamar desde cualquier momento del código, fuera de la clase, que nos interese de la siguiente manera, por ejemplo:

puts animal.nombre ó puts animal.genero ó puts animal.especie

El código quedaría así:

class Animal
def initialize (nombre, genero, especie)
@nombre = nombre
@genero = genero
@especie = especie
end

def nombre
@nombre
end

def genero
@genero
end

def especie
@especie
end

end

animal = Animal.new('Perro', 'Cánido', 'Canis Lupus Familiaris')
puts animal
puts animal.nombre
puts animal.genero
puts animal.especie

animal = Animal.new('Gato', 'Felino', 'Felis Silvestris Catus')
puts animal
puts animal.nombre
puts animal.genero
puts animal.especie

Al ejecutar este código nos dará la siguiente salida:

<Animal:0x0142e57c>
Perro
Cánido
Canis Lupus Familiaris
<Animal:0x0142e464>
Gato
Felino
Felis Silvestris Catus

Como se ve, esto es una manera de acceder a estos parámetros de cada objeto y vemos que tienen la misma estructura.

A este procedimiento se le conoce como método getter, que viene a decir... coger el dato, tan sólo lo lee y te lo muestra al llamar al método.

Si quisiéramos modificar el dato, entonces tendríamos que llamar a la variable que lo identifica con un método setter, que viene a significar... pon esto...

Imaginemos que queremos preguntar al usuario cada dato, entonces este se metería como variable de instancia concreto del objeto.

Vamos a crear el objeto animal pidiendo los datos nombre, género y especie.

class Animal

def initialize (nombre, genero, especie)
@nombre = nombre
@genero = genero
@especie = especie
end

def nombre
@nombre
end

def nombre=(value)
@nombre=value
end

def genero
@genero
end

def genero=(value)
@genero=value
end

def especie
@especie
end

def especie=(value)
@especie=value
end

end

puts "¿Qué animal es?"
STDOUT.flush
n=gets.chomp

puts "¿Qué género es?"
STDOUT.flush
g=gets.chomp

puts "¿Qué especie es?"
STDOUT.flush
e=gets.chomp

animal = Animal.new(n, g, e)
puts "El animal es #{animal.nombre} del género #{animal.genero} y de la especie #{animal.especie}"

Lo cual nos preguntará :
nombre: Perro
género: Cánido
Especie: Lupus

y contestará con la frase:
"El animal es Perro del género Cánido y de la especie Lupus"

2.- Con los accesores: Hay otra forma de acceder a las variables es con los accesores a través de un método concreto denominado attr_ que pueden ser de tres tipos, el attr_accessor, el attr_writer y el attr_reader.

attr_accessor: esta es una instrucción que permite leer y modificar la variable de instancia y simplifica mucho ya que sólo tenemos que hacer la sintaxis:

attr_xxxxx :nombre de la variable1, :nombre de la variable2 ...

en el caso de atr_accessor sustituiría a los dos métodos tanto el setter como el getter, es decir...

def especie #Este es el getter
@especie
end

def especie=(value) #Este es el setter
@especie=value
end


por

attr_accessor :nombre, :genero, :especie

de manera que el código haría lo mismo y quedaría como sigue:
class Animal

def initialize (nombre, genero, especie)
@nombre = nombre
@genero = genero
@especie = especie
end

attr_accessor :nombre, :genero, :especie

end

puts "¿Qué animal es?"
STDOUT.flush
n=gets.chomp

puts "¿Qué género es?"
STDOUT.flush
g=gets.chomp

puts "¿Qué especie es?"
STDOUT.flush
e=gets.chomp

animal = Animal.new(n, g, e)
puts "El animal es #{animal.nombre} del género #{animal.genero} y de la especie #{animal.especie}"

Con attr_writer sólo será accesible a escribir la variable pero no para ser leída.
Con attr_reader sólo estará accesible a ser leída pero no podrá ser modificada.

El método 1 se usa cuando hay constructor con el fin de dejar bien atados los valores de las variables de instancia, en el caso de que no sean variables fijas, puede simplificarse con los accesores.

****************

2.- Las clases  mismas:

Todo lo escrito hasta aquí es válido para la clase misma, que es en sí mismo un objeto desde el que salen otros objetos según sean definidos en ella.

La clase misma también es un objeto, y se aplican las mismas premisas que para los objetos pero con las salvedades que:

  • Sus variables empiezan con @@
  • Los métodos que los llaman son self.def... end
  • Se accede a ello mediante nombredelaclase.métododelaclase
Pero las variables de instancia pueden ponerse en la clase o en los métodos de los objetos, en ambos casos hacen caso a alcances distintos, de manera que una misma variable de instancia se puede poner para la clase y no a los objetos que se creen.

En el caso de los animales, podemos decir que por defecto sean de color rojo, pero sin es perro que sea negro para ese nombre de animal.

Veamos el código:

class Animal
@color="Rojo verdoso"          # variable de clase
def self.color_animal
puts @color
end
def color_animal
puts @color
end
end

Animal.color_animal              # accede a la clase
Animal.new.color_animal       # accede al objeto

Al ejecutar este código sólo dará la salida "Rojo verdoso" pues la variable de instancia está en el nivel de la clase y no del objeto y se accede con self.

Sin embargo, si tendrá valores distintos si se le asigna un valor distinto dentro de un método de objeto con el mismo nombre que la instancia de la clase:

class Animal
@color="Rojo verdoso"          # variable de clase
def self.color_animal
puts @color
end
def color_animal
@color="Rojo azulado"
puts @color
end
end

Animal.color_animal              # accede a la clase
Animal.new.color_animal       # accede al objeto

La salida que da es:
Rojo verdoso
Rojo azulado

Ahora bien, la variable de clase es accesible tanto desde la clase misma como desde los métodos de instancia:

class Animal
@@color="Rojo verdoso"          # variable de clase
def self.color_animal                  # método de clase por llevar self
puts @@color
end
def color_animal                         #método de instancia por no llevar self
puts @@color
end
end

Animal.color_animal              # accede a la clase
Animal.new.color_animal       # accede al objeto

Este código da dos líneas con el contenido "Rojo verdoso" aunque se acceda desde la instancia.

Vallamos con más cuestiones de las clases en la PARTE 2