Programación en Ruby/Programación orientada a objetos
Escribiendo nuestras propias clases
Hasta ahora hemos usado el estilo de programación procedural (que todavía se utiliza en lenguajes como C) para escribir nuestros programas. Programar de manera procedural significa que nos enfocamos en los pasos necesarios para completar la tarea sin prestar atención a cómo son manipulados los datos.
En el estilo de programación Orientado a objetos, los objetos son tus agentes, tus proxies, en el universo de tu programa. Tu les pides información, les asignas tareas, les pides que realicen cálculos y te los reporten y los comunicas entre si para que trabajen juntos. Cuando diseñes una clase, piensa en los objetos que van a ser creados de ella. Piensa acerca de lo que los objetos conocen y las cosas que hacen.
Las cosas que un objeto sabe de si mismo se llaman variables de instancia (instance variables). Representan el estado de un objeto (los datos, por ejemplo, la cantidad y el id de un producto) y pueden existir valores únicos para cada instancia de la clase. Las cosas que un objeto puede hacer se llaman métodos. Un objeto es una entidad que sirve como contenedor de datos y también controla el acceso a los mismos. Asociados con el objeto, hay una serie de atributos que esencialmente no son más que variables que le pertenecen al objeto. También asociadas con el objeto, hay una serie de funciones que proveen una interface para la funcionalidad del objeto, llamadas métodos. Hal Fulton Una clase es una combinación de estado y métodos y es utilizada para construir objetos. Es una especie de plano para un objeto. Por ejemplo, es posible que utilices una clase llamada Boton para hacer decenas de botones diferentes y cada uno puede tener su propio color, tamaño, forma, etiqueta, etc. Un objeto es una instancia de una clase.
Lee esto con atención!
Las clases en ruby son objetos de primera clase, cada una es una instancia de la clase Class. Cuando una nueva clase es definida (de manera típica usando class Nombre end), un objeto de clase Class es creado y asignado a una constante (Nombre en este caso). Cuando llamamos Nombre.new para crear un objeto, el método de instancia en la clase Class es llamado, que a su vez llama al método allocate que asigna memoria para el objeto antes de finalmente llamar el método initialize. Las fases de construcción e inicialización de un objeto son independientes y ambas pueden ser sobreescritas. La inicializacón se hace mediante el método de instancia initialize mientras que la construcción se hace vía el método de clase new. initialize no es un constructor!. La siguiente Jerarquía de clases es informativa. Vamos a escribir nuestra primera clase simple. p029perro.rb
1 # definicion de la clase Perro 2 class Perro 3 def initialize(raza, nombre) 4 # Variables de instancia 5 @raza = raza 6 @nombre = nombre 7 end 8 9 def ladra 10 puts 'Ruff! Ruff!' 11 end 12 13 def muestra 14 puts "Soy de raza #{@raza} y mi nombre es #{@nombre}" 15 end 16 end 17 18 # Crear un objeto 19 # Los objetos son creados en el 'heap' 20 p = Perro.new('Labrador', 'Benzy') 21 22 =begin 23 Cada objeto 'nace' con ciertas habilidades innatas. 24 Para ver una lista de los métodos con los que nace un objeto 25 puedes llamar el metodo methods 26 =end 27 puts p.methods.sort 28 29 # Entre todos estos metodos, oject_id y respond_to? son 30 # importantes. Cada objeto en ruby tiene asociado un numero 31 # unico. 32 puts "El id del objeto es #{p.object_id}." 33 34 # Para saber si un objeto sabe como responder a un mensaje 35 # puedes usar el metodo respond_to? 36 if p.respond_to?('habla') 37 p.habla 38 else 39 puts 'Lo siento, el objeto no entiende el mensaje habla.' 40 end 41 42 p.ladra 43 p.muestra 44 45 # hacer que p y p1 hagan referencia al mismo objeto 46 p1 = p 47 p1.muestra 48 49 # hacer que p haga referencia a nil 50 p = nil 51 52 53 # Si ahora declaramos: 54 p1 = nil 55 # entonces el Objeto de clase perro es abandonado y es 56 # elegible para la recoleccion de basura (GC)
Si ejecutamos el programa, además de la lista de métodos con los que nace el objeto 1, obtenemos como resultado:
El id del objeto es 281140. Lo siento el objeto no entiende el mensaje habla. Ruff! Ruff! Soy de la Raza Labrador y mi nombre es Benzy Soy de la Raza Labrador y mi nombre es Benzy
El método new es usado para crear un objeto de clase Perro. Los objetos son creados en el heap. La variable p es conocida como una variable de referencia. No guarda el objeto en si, sino algo parecido a un apuntador o una dirección del objeto. Utilizamos el operador punto (.) en una variable de referencia para decir, "utiliza lo que está antes del punto para traerme lo que está después del punto". Por ejemplo: p.ladra.
Si estás escribiendo una aplicación Rails en la que uno de tus modelos es, digamos, Cliente, entonces cuando escribes el código que hace que las cosas pasen (un cliente accesando un sitio, actualizando un número de teléfono de un cliente, agregando un artículo a un carrito de compras), con toda seguridad estarás enviando mensajes a objetos de la clase Cliente. Aún recién creado, un objeto no está completamente en blanco. Tan pronto como un objeto comienza a existir, responde a algunos mensajes. Cada objeto 'nace' con ciertas habilidades innatas. Para ver una lista de los métodos con los que nace un objeto, puedes usar el método methods.
Es posible determinar de antemano (antes de pedirle a un objeto que haga algo) si un objeto es capaz de manejar determinado mensaje usando el método respond_to?. Este método existe para todos los objetos; puedes preguntarle a cualquier objeto si responde a cualquier mensaje. respond_to? aparece usualmente en conexión con la condicion lógica if.
1 class Perro 2 def initialize(raza, nombre) 3 @raza = raza 4 @nombre = nombre 5 end 6 end 7 8 p = Perro.new('Alsacian', 'Lassie') 9 puts p.class.to_s
El resultado es:
RubyMate r8136 runnin Ruby r1.8.6 (/usr/local/bin/ruby) >>> perro2.rb
perro
instance_of? regresa true si el objeto es instancia de la clase especificada.
1 num = 10 2 puts (num.instance_of? Fixnum) # true
Las clases abiertas de Ruby
En Ruby, las clases nunca son cerradas: siempre puedes agregar métodos a una clase que ya existe. Lo anterior aplica tanto para las clases que tu escribes como para las clases estándar incluídas en Ruby. Todo lo que tienes que hacer es abrir la definición de una clase existente y el nuevo contenido que especifiques va a ser agregado. En el ejemplo pasado, definimos la clase Motocicleta en el archivo p030motocicleta.rb.
1 class Motocicleta 2 def initialize(marca, color) 3 # Variables de instancia 4 @marca = marca 5 @color = color 6 end 7 def arranca 8 if (@engineState) 9 puts 'Motor encendido' 10 else 11 @engineState = true 12 puts 'Motor apagado' 13 end 14 end 15 end
Posteriormente, en el archivo p031pruebamotocicleta 'abrimos' la clase Motocicleta y definimos el método describe.
1 require 'p030motocicleta' 2 m = Motocicleta.new('Yamaha', 'rojo') 3 m.arranca 4 5 class Motocicleta 6 def describe 7 puts 'El color de la motocicleta es ' + @color 8 puts 'La marca de la motocicleta es ' + @marca 9 end 10 end 11 m.describe 12 m.arranca 13 puts self.class 14 puts self 15 puts Motocicleta.instance_methods(false).sort
Otro ejemplo: en el archivo p031babreperro.rb abrimos la clase Perro que definimos anteriormente en el archivo p029perro.rb y agregamos el método ladra_fuerte. (Observa que para poder abrir la clase, necesitamos requerirla primero):
1 # primero necesitamos requerir el archivo 2 # donde se encuentra la definicion de la clase 3 # perro 4 require 'p029perro.rb' 5 6 # abrimos de la clase y definimos un método nuevo 7 class Perro 8 def ladra_fuerte 9 puts 'Woof! Woof!' 10 end 11 end 12 13 # inicializamos una instancia de la clase 14 d = Perro.new('Labrador', 'Benzy') 15 16 # podemos usar los metodos previamente definidos 17 # y el que acabamos de definir 18 d.ladra 19 d.ladra_fuerte 20 d.muestra
Si ejecutas este programa (y has seguido paso a paso los temas anteriores), el resultado tal vez no sea lo que tu esperas:
Intance_variables is_a? kind_of? ladra method methods muestra nil object_id private_methods protected_methods public_methods respond_to? send singleton_methods taint tainted? to_a to_plist to_s type untaint El id de objeto es 280360 Lo siento, el objeto no entiende el mensaje habla. Ruff!Ruff! Soy de la Raza Labrador y mi nombre es Benzy Soy de la Raza Labrador y mi nombre es Benzy Ruff!Ruff! Woof!Woof! Soy de la Raza Labrador y mi nombre es Benzy
¿Qué está pasando?, ¿Por qué esa larga lista de métodos si en el archivo que estamos ejecutando (p31abreperro.rb) sólo llamamos d.ladra, d.ladra_fuerte y d.muestra?.
La respuesta es simple pero importante: al inicio del programa estamos incluyendo el archivo p029perro, por lo tanto todo el codigo de ese archivo es ejecutado, no sólo la definición de la clase.
Observa que en el archivo p029perro.rb hacemos llamadas a métodos que imprimen a STDOUT (ed, llaman al método puts).
A continuación vemos otro ejemplo en donde agregamos un método a la clase String. (p032micadena).1
1 class String 2 def invierte 3 puts self.reverse 4 end 5 end 6 cadena = "La ruta nos aporto otro paso natural!" 7 cadena.invierte
1 El método invierte no es en realidad muy útil, pero sirve para ilustrar el punto.
La facilidad que tenemos de abrir cualquier clase, incluso las clases built-in como String y Numeric es un tema medular en el estudio de Ruby y en la manera en que algunos programas en Ruby están escritos.
Rails hace uso extenso de esta característica de Ruby en el módulo ActiveSupport. De hecho, cuando eres nuevo en Ruby y en Rails, puede ser confuso identificar qué métodos son estándar en Ruby y qué métodos son definidos por Rails.
Por ejemplo, en el archivo lib/core_ext/integer/even_odd.rb, dentro de ActiveSupport, se encuentra la definición del el método multiple_of? dentro del módulo EvenOdd. (En realidad, el módulo EvenOdd está anidado dentro de otros módulos pero para simplificar el ejemplo, eliminamos la ruta completa):
1 module EvenOdd 2 def multiple_of?(number) 3 self % number == 0 4 end 5 end 6
Nota que hasta aquí, no se ha abierto ninguna clase de Ruby. Sin embargo, en el archivo lib/core_ext/integer/integer.rb encontramos:
1 # primero el archivo even_odd es requierido 2 require 'active_support/core_ext/integer/even_odd' 3 4 # se a bre la clase Integer 5 class Integer 6 # se incluye el módulo Integer 7 # recuerda que para sipmlificar el ejemplo, 8 # eliminamos el identificador completo del módulo 9 # EvenOdd 10 include EvenOdd 11 end 12 13 # Toma en cuenta que esto es esencialmente lo mismo que 14 # escribir: 15 # 16 # 17 # class Integer 18 # def multiple_of?(number) 19 # self % number == 0 20 # end 21 # end 22 # 23 # Los metodos definidos en un modulo 24 # son agregados como metodos de instancia 25 # a cualquier clase en la que incluyamos dicho 26 # modulo usando el keyword include. (mixins) 27 28
ActiveSupport es un ejemplo excelente de cómo podemos abrir las clases en Ruby y agregar métodos. Si quieres explorar más en este tema, analiza la librería Facets que contiene muchos ejemplos útiles, incluyendo algunos de los que se usan en Rails (en particular Facets/CORE).