Clases y Objetos
Las clases y los objetos son los conceptos más importantes de la Programación Orientada por Objetos (POO), y están fuertemente relacionados. Los objetos se crean a partir de clases, y las clases sirven como plantillas para crear objetos. A los objetos también se les llama instancias de clase.
Utilicemos una analogía. Piensa en la palabra persona. Cuando hablamos de una persona no nos referimos a ninguna persona en particular, solo a la idea de la persona. Una persona tiene un nombre, una edad, una estatura, etc. Esa es la clase, la plantilla.
Ahora piensa en personas específicas: tu, tu mamá, el presidente de tu país, Alan Turing, Nelson Mandela, etc. Esos son los objetos. Para cada persona puedes llenar los datos de la plantilla: el nombre, la edad, la estatura, etc.
Veamos cómo definir una clase en Ruby. Crea un archivo llamado person.rb
y escribe lo siguiente:
Esa es la definición más simple de una clase. Comienza con la palabra clave class
, seguida de el nombre que le quieras dar a la clase y termina con la palabra clave end
. Dentro de la clase van los métodos (comportamiento) y atributos (información) que van a tener los objetos que se creen a partir de esta clase, pero en este ejemplo aún no tenemos ninguno.
Para crear objetos a partir de una clase se utiliza el nombre de la clase seguido de la palabra clave new
:
Esta línea retorna el objeto, que usualmente se almacena en una variable:
La variable la puedes llamar como quieras. También puedes crear varios objetos de esta clase, de hecho todos los que quieras mientras que no se llene tu memoria RAM:
En este ejemplo estamos creando 10,003 personas: p1
, p2
, p3
y las 10,000 que están dentro del arreglo almacenado en la variable muchas_personas
.
Podríamos crear más instancias de Person
, de hecho podíamos crear 7 billones de instancias y, quizá, terminar simulando la realidad dentro del computador (o quizá alguien ya hizo eso y nosotros no somos más que ... una simulación!). Pero bueno, no nos desviemos del tema, necesitamos aprender varios conceptos antes de eso. (Aunque lo más interesante sería crear una simulación dentro de una simulación! Ya veremos).
Comportamiento (métodos)
Hasta ahora nuestra clase Person
no es muy útil. Podemos agregarle un primer comportamiento: saludar.
Los comportamientos son métodos que defines dentro de la clase, pero para llamar el método, tienes que hacerlo sobre un objeto, como en el siguiente ejemplo:
Para llamar un método sobre un objeto debes agregar un punto seguido del nombre del método (p.e. p1.greet
). A estos métodos también se les conoce como métodos de instancia, porque la única forma de invocarlos es sobre un objeto. (Más adelante veremos métodos que se pueden llamar directamente sobre la clase, sin necesidad de crear un objeto, pero no nos adelantemos).
Los métodos de instancia también pueden recibir argumentos:
Visibilidad de los métodos
También es posible crear métodos privados, es decir, métodos que solo se pueden acceder internamente por otros métodos. La forma de hacerlo es la siguiente:
Para crear métodos privados debes utilizar la palabra clave private
. Todos los métodos que estén debajo de esa palabra serán privados y no se podrán acceder desde afuera:
Lo mismo ocurre si intentamos acceder a another_secret_method
.
El constructor
Las clases pueden tener un método especial llamado initialize
que se llama cada vez que se crea un objeto con .new
. Por ejemplo, supongamos que queremos imprimir “creando nueva persona …” cada vez que se crea un objeto de tipo Person
:
A ese método initialize
se le conoce como el constructor. Como buena práctica te recomiendo ubicarlo siempre encima de cualquier otro método de tu clase.
El constructor también puede recibir argumentos:
Ahora cuando crees una persona debes pasarle el argumento:
En este momento no estamos almacenando el nombre de la persona en ninguna parte. Pero eso es precisamente de lo que vamos a hablar a continuación.
Atributos (información de un objeto)
Así como un objeto tiene un comportamiento, los objetos también pueden tener información, o atributos (hablo de objeto y no de clase porque, aunque los métodos se definen en la clase, se llaman sobre los objetos).
Como decíamos antes, una persona puede tener un nombre, una edad, una estatura, etc. Esos son los atributos.
Imagina a los atributos como variables que están asociadas a un objeto. De hecho, a los atributos se les conoce como variables de instancia (instancia se refiere a un objeto, la instancia de una clase).
En Ruby vas a identificar los atributos en una clase porque comienzan con el carácter @
. Por ejemplo, podemos almacenar el argumento que llega en el constructor dentro de un atributo de Person
:
Analiza este último ejemplo con cuidado. El constructor está recibiendo un argumento llamado name
y almacenamos el valor en el atributo @name
. name
no es lo mismo que @name
.
La ventaja de guardar el nombre en @name
, es que hora lo podemos utilizar desde cualquier otro método:
Fíjate cómo estamos utilizando @name
dentro del método greet
. Creemos una instancia (objeto) de Person
para probarlo:
Visibilidad de los atributos
El atributo @name
solo puede ser leído y modificado dentro de instancias de Person
. Si queremos exponerlo al mundo exterior tenemos que crear métodos para leerlo y modificarlo:
En este ejemplo hemos creado dos métodos: name
y name=
(más abajo entenderás por qué lo llame así). El método name
retorna el valor de @name
mientras que name=
recibe un argumento y modifica @name
.
Ahora podemos utilizar esos métodos para leer y modificar el nombre desde afuera del objeto. Por ejemplo:
Fíjate en la línea p1.name=(“Mary”)
. El nombre de ese método es especial porque nos permite hacer lo siguiente:
A esto se le conoce como operator overloading en Ruby por si quieres investigar más en Internet.
Supongamos ahora que le vamos a agregar los atributos age
y gender
a Person
. Sería muy engorroso tener que crear el método de lectura y de escritura para cada uno de los atributos.
Ruby nos ofrece unos atajos para no tener que escribir tanto código: attr_accessor
, attr_reader
y attr_writer
.
Estos atajos se utilizan de la siguiente forma. Supongamos que tenemos los atributos name
, age
y gender
. Podemos crear los métodos para leer y escribir estos atributos así:
La línea attr_accessor :name, :age, :gender
crea los métodos para leer y escribir los atributos por nosotros (utilizando una técnica que se llama metaprogramming en Ruby). Ya no es necesario crear los métodos name
, name=
, age
, age=
, gender
y gender=
manualmente.
Pero supongamos que el atributo age
no debería ser posible modificarlo directamente, podemos utilizar attr_reader
para que Ruby solo cree el método age
y no age=
:
Hice las siguientes modificaciones a nuestro Person
:
Ahora el atributo
age
es de solo lectura.Cambié el nombre del argumento en el constructor de
age
ainitial_age
para que sea más claro.Creé un método
grow
que incrementa la edad en 1.
Ahora ya no es posible modificar el atributo age
directamente desde afuera del objeto:
Ahora supongamos que nos piden que el atributo gender
sea solo de escritura, no de lectura, porque, no sé, estaba ocasionando discriminación de genero. Podemos utilizar attr_writer
para que Ruby solo cree el método de escritura gender=
y no el de lectura gender
:
Ahora no va a ser posible leer el atributo gender
desde fuera del objeto:
Se empieza a volver más denso cada vez ¿no? Es fácil perderse en los detalles, pero no olvides lo siguiente:
Una clase es una plantilla para crear objetos.
Un objeto es una instancia de una clase.
La clase puede tener un método
initialize
llamado constructor que se va a llamar cada vez que se cree un objeto de esa clase.El constructor se utiliza generalmente para inicializar los atributos de los objetos.
En la clase puedes definir métodos (el comportamiento) y atributos (información) que va a tener los objetos. A los atributos se les llama variables de instancia y a los métodos se les llama métodos de instancia
Los atributos son información asociada a cada objeto.
Los atributos son privados y solo pueden ser leídos y modificados dentro del objeto, a menos de que crees métodos para leerlos y modificarlos.
Puedes utilizar los atajos
attr_accessor
,attr_reader
yattr_writer
para que Ruby cree los métodos para leer y modificar los atributos.
En Ruby (casi) todo es un objeto
Si has trabajado con Ruby seguramente ya has trabajado con clases, objetos y métodos. Por ejemplo, cuando haces algo como "Hola".length
estás llamando el método length
de una clase llamada String
. De hecho podemos ser más explicitos y crear la cadena de la siguiente forma:
Cuando creas una cadena con comillas, Ruby lo traduce a String.new("...")
. Lo mismo ocurre con los arreglos:
Cuando creas un arreglo con corchetes cuadrados, Rubylo traduce a Array.new
. También los hashes:
Los números también son objetos en Ruby y puedes llamar métodos sobre ellos:
De hecho, cuando sumas dos números en Ruby (p.e. 1 + 2
) realmente estás llamando el método +
sobre el primer número y le estás pasando el segundo número como argumento!
Conociendo la clase de un objeto
Todos los objetos tienen un método especial llamado class
que retorna la clase que se utilizó para crearlos:
self
Por último, es muy probable que veas la palabra clave self
en las clases. Veamos para qué sirve con un ejemplo:
Con self
podemos ser más explícitos para referirnos a un método del objeto. En este caso, estamos almacenando el resultado del método random_number
en una variable random_number
. Como tienen el mismo nombre utilizamos self
para indicarle a Ruby que nos estamos refiriendo al método y no a la variable.
self
se utiliza generalmente cuando hay colisiones de nombres entre un método y una variable.
Suficiente información por este capítulo. En el siguiente capítulo pondremos en práctica todos estos conocimientos.
Ejercicios
Crea una clase llamada
Square
.Agrega un constructor a
Square
que reciba un argumentolength
y asígnalo a un atributo con el mismo nombre.Agrega métodos para leer y modificar el atributo
length
.Agrega un método llamado
area
que retorne el área del cuadrado.Agrega un método llamado
perimeter
que retorne el perímetro del cuadrado.Crea 2 instancias de
Square
, una con longitud de 5 y la otra con longitud de 10. Verifica que retornen el área y la longitud correcta.
Last updated