Ruby Modules I: Qué son y para qué sirven.

Como vereis los habituales, he cambiado un poco el tema de WordPress para que sea más ancho que los fragmentos de código no tengan lineas partidas. Y ahora al tema de la semana (porque voy a intentar escribir al menos un post por semana)

Quiero introducir los módulos, porque son una característica de Ruby que los que como yo, que se acostumbraron a la programación orientada a objetos con Java, no solemos usar, y tendemos a usar la herencia.
Sabemos que existen, que hacen cosas, pero no les damos uso. Yo el primero. No es manía, es simplemente que no estamos acostumbrados a ellos, y no caemos que a veces son la mejor solución. Llevo unos días haciendo uso intensivo de ellos porque estoy haciendo un refactor de una aplicación, y me estoy arrepintiendo de no haberlos usado desde el principio.

Primeramente, explicar que un módulo, es basicamente una clase. Tienen constantes, metodos de clase, métodos de instancia, y todo lo que cabe esperar de una clase, con la salvedad de que no se pueden instanciar ni derivar(hacer subclases). Se definen así:

module myModulo
    def metodo1
        # Do stuff
    end
    # . . .
end

En cuanto a su objetivo, se diferencian en que no están hechos para modelar un problema, como las clases propiamente dichas, sino que están pensado a servir como archivadores (librerías), donde introducir métodos y variables que no queremos meter en las clases. Generalmente agrupados por tener algo en común. El ejemplo que todo tutorial online pone, y yo no voy a ser original, es el modulo Math.
Math define en su interior constantes y funciones relacionadas con cálculos numéricos, como el número PI, el número E, o funciones como senos, logaritmos, arcotangentes y otras obras del demonio.
Ese es su uso numero 1. El más frecuente y el más conocido.

Su segundo uso – que no sabía si incluir en el primero, porque es casi una consecuencia de él – es agrupar en “namespaces”, ampliando así el numero de nombres de variables que podemos tener, y evitar conflictos de nombre.
Por poner un ejemplo, podemos tener los siguientes módulos con el mismo método half

module StringStuff
  def self.half(string)
    self.length/2
  end  
end

module IntegerStuff
  def self.half(num)
    num/2
  end
end

Uno devuelve un número dividido entre dos, y otro la longitud de un String dividido entre 2. Se llaman igual, pero no entran en conflicto, y los invocamos mediante el operador unario ::

StringStuff::half("abcdefgh") #=> 4
IntegerStuff:half(34) #=> 17

El tercer uso de los módulos es el conocido como MIXIN. Algunos lenguajes, como C++, permiten algo llamado herencia múltiple. Como su nombre indica, consiste en que una clase herede de varias. Por ejemplo, un pato, que heredase de la clase Ave, y de la clase AviónDeCombate, podría poner huevos y disparar misiles guiados por calor a edificios civiles. Mola, eh?
Pero Ruby no soporta esto. Ni java. Y muchos otros lenguajes tampoco. ¿Porqué no lo soportan? Si parece genial!! Pues porque es propenso a errores. Por ejemplo, la clase Ave tiene un método Volar, pero la clase AviónDeCombate también lo tiene, y es muy diferente. ¿Cuál debe heredar el pato?
Además, cuando ambas clases padre tenían a su vez un ancestro común, la cosa se complicaba aun más.
Por eso, por seguridad, casi todos los lenguajes OO actuales no permiten herencia múltiple.

Sin embargo, en ruby, implementado módulos, se puede simular la herencia múltiple.
Un mixin (literalmente mezcla) es una clase que implementa un módulo. Una clase puede implementar un módulo o extenderlo. Voy a dejar las diferencias, de lado, son sutiles. Centrémonos en la implementación.

Un ejemplo animal. Son los más gráficos.
Queremos implementar las aves.Hay aves que vuelan (Paloma, Pato), aves que no vuelan(Avestruz, Pingüino), aves que nadan(Pingüino, Pato), y aves que no nada (Paloma, Avestruz).
Si queremos reusar código, podemos hacerlo con las características básicas (plumas, pico, alas) y algunos métodos (Caminar sobre dos patas), pero sus métodos pueden diferir mucho y no se puede modelar esta situación usando herencia pura.
Si creamos una subclase AvesNadadoras y otra AvesVoladoras, el pato está en ambas. Y así con cualquier combinación de esos pájaros que he puesto como ejemplo. La solución para esto es usar módulos.
Si agrupamos las habilidades relacionadas en módulos, es realmente sencillo.

module AirSkills
  def fly
    "I'm fliying"
  end  
  def land
    "I'm tired of flying. I stop."
  end
end

module WaterSkills
  def swim
    "I'm swimming"
  end
  def dive
    "Glu glug glub glud"
  end
end

class Bird
  def initialize
    @wings = 2
    @legs = 2
  end
  def walk
    "I'm walking"
  end
end

class Penguin < Bird
  include WaterSkills  # Penguins can walk, swim and dive
  def walk
    "I'm walking very funny!"                       # Overrides Bird's walk
  end
  def dive
    "Look mummy!I'm diving better than the duck!"   # Overrides WaterSkills' dive
  end
end

class Dove < Bird     # Dove significa paloma. Lo aprendí mientras me inventaba el ejemplo.
  include AirSkills  # Doves can walk, fly and land
end

class Ostrich < Bird  # Tampoco sabía traducir avestruz. Mi inglés tiene lagunas en cuanto a aves. 
  # Ostrichs only can walk. No flying. No swimming.
end

class Duck < Bird
  include WaterSkills, AirSkills  # Ducks can do everything. They rock!
end

Cero duplicaciones de código. Las clases Pato, Avestruz y Paloma no necesitan ni una simple línea de código, y Pingüino sólo para sobrescribir el método caminar heredado de pájaro, y el nadar, implementado a través del módulo WaterSkills.

El próximo artículo irá sobre cómo usar los mixins para añadir métodos a las clases en lugar de a las instancias, que es un poco más enrevesado pero tremendamente útil.

Anuncios

, , , , ,

  1. Deja un comentario

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: