Al empezar a escribir programas más y más grandes de Ruby, se encontrará, naturalmente, la produc- ción de trozos de código reutilizables --librerías de rutinas relacionadas que son de aplicación general. Usted querrá separar este código en archivos separados para que los contenidos puedan ser compartidos entre los diferentes programas de Ruby.
A menudo, este código se organiza en clases, así que probablemente se quedará con una clase (o un conjunto de clases relacionadas entre sí) en un archivo.
Sin embargo, hay ocasiones en las que deseará agrupar algunas cosas que, naturalmente, no forman una clase.
Una primera aproximación puede ser la de poner todas estas cosas en un archivo y simplemente car- gar ese archivo en cualquier programa que lo necesite. Esta es la forma en la que el lenguaje C funciona. Sin embargo, este enfoque tiene un problema. Digamos que hay que escribir un conjunto de funciones de trigonometría sin, cos, etc. Se meten todos en un archivo, trig.rb para las generaciones futuras y a
disfrutar. Mientras tanto, Sally está trabajando en una simulación del bien y del mal y en los códigos de un conjunto de sus propias rutinas útiles, incluyendo be_good (ser_bueno) y sin (pecado), y las pone en moral.rb. Joe, que quiere escribir un programa para saber cuántos ángeles pueden bailar en la cabeza
de un alfiler, carga tanto trig.rb como moral.rb en su programa. Pero ambos definen un método lla-
mado sin. Malas noticias.
La respuesta es el mecanismo de módulo. Los módulos de definen un espacio de nombres (namespace ), una caja en la que sus métodos y constantes pueden correr sin tener que preocuparse de ser pisados por otros métodos y constantes. Las funciones trigonométricas pueden ir en un módulo:
module Trig PI = 3.141592654 def Trig.sin(x) # .. end def Trig.cos(x) # .. end end
y los métodos sobre la buena y la mala “moral” pueden ir en otro.
module Moral VERY_BAD = 0 BAD = 1 def Moral.sin(badness) # ... end end
Las constantes de módulo se designan como constantes de clase, con una letra mayúscula inicial. Las definiciones de método es similar: los métodos del módulo se definen como métodos de clase.
Si un programs de terceros quiere usar estos módulos, simplemente puede cargar los dos archivos (utilizando la declaración Ruby require, que se discute más adelante) y referenciar a los nombres cuali-
ficados.
require ‘trig’ require ‘moral’
y = Trig.sin(Trig::PI/4)
wrongdoing = Moral.sin(Moral::VERY_BAD)
Al igual que con los métodos de clase, llamar a un método de módulo precediendo su nombre con el nombre del módulo y un punto, y se hace referencia a una constante con el nombre de módulo y dos sig- nos de dos puntos.
Mixins
Los módulos tienen otro uso extraordinario. De un golpe prácticamente eliminan la necesidad de heren- cia múltiple proporcionando un servicio llamado mixin.
En los ejemplos de la sección anterior, hemos definido los métodos de módulo, métodos cuyos nom- bres fueron precedidos por el nombre del módulo. Si esto le hizo pensar en los métodos de clase, su si- guiente pensamiento podría ser “¿qué pasa si yo defino los métodos de instancia dentro de un módulo?” Buena pregunta. Un módulo no puede tener instancias, ya que no es una clase. Sin embargo, puede incluir un módulo dentro de una definición de clase. Cuando esto sucede, todos los métodos de instancia del módulo están de pronto disponibles como métodos de la clase también. Se mezclan (mixed in). De hecho, el mezclado en los módulos efectivamente hace que se comporten como superclases.
module Debug def who_am_i?
“#{self.class.name} (\##{self.id}): #{self.to_s}” end end class Phonograph include Debug # ... end class EightTrack include Debug # ... end
ph = Phonograph.new(“West End Blues”) et = EightTrack.new(“Surrealistic Pillow”)
ph.who_am_i? -> “Phonograph (#935520): West End Blues” et.who_am_i? -> “EightTrack (#935500): Surrealistic Pillow”
Al incluir el módulo Debug, tanto Phonograph como EightTrack ganan el acceso al método de ins-
tancia who_am_i? .
Vamos a ver un par de puntos acerca de la instrucción include antes de continuar. En primer lugar,
no tiene nada que ver con los archivos. Los programadores de C utilizan la directiva de preprocesador llamada #include para insertar el contenido de un archivo en otro durante la compilación. El include de
Ruby simplemente hace referencia a un nombre de módulo. Si este módulo se encuentra en un archivo separado, deberá utilizar require (o su primo menos usado, load) para rastrear el archivo antes de uti-
lizar include. En segundo lugar, un include de Ruby no sólo tiene que copiar los métodos del módulo
de instancia en la clase. Por el contrario, hace una referencia desde la clase al módulo incluido. Si varias clases incluyen el módulo, todos ellas apuntan al mismo. Si cambia la definición de un método dentro de
un módulo, incluso cuando el programa se está ejecutando, en todas las clases que incluyan a este mó- dulo se presentan el nuevo comportamiento (Por supuesto, aquí estamos hablando sólo de los métodos. Las variables de instancia son siempre por objeto, por ejemplo).
Los mixins le dan una forma extraordinariamente controlada de añadir funcionalidad a las clases. Sin embargo, su verdadero poder viene cuando el código en el mixin empieza a interactuar con el código de la clase que lo utiliza. Tomemos el mixin estándar de Ruby Comparable como ejemplo. Usted puede
utilizar el mixin Comparable para añadir los operadores de comparación (<, <=, ==,>=, y >), así como el
método between?, en una clase. Para que esto funcione, Comparable asume que cualquier clase que le
utilice define el operador <=>. Por lo tanto, como escritor de clase, se define un método <=>, incluyendo Comparable y se obtienen seis funciones de comparación de forma gratuita. Vamos a probar esto con
nuestra clase Song, para hacer las canciones comparables en función de su duración. Todo lo que tene-
mos que hacer es incluir el módulo Comparable y poner en práctica el operador de comparación <=>. class Song
include Comparable
def initialize(name, artist, duration) @name = name @artist = artist @duration = duration end def <=>(other) self.duration <=> other.duration end end
Podemos comprobar que los resultados son sensibles con una prueba de algunas canciones.
song1 = Song.new(“My Way”, “Sinatra”, 225) song2 = Song.new(“Bicylops”, “Fleck”, 260) song1 <=> song2 -> 1
song1 < song2 -> true song1 == song1 -> true song1 > song2 -> false