Se me ha ocurrido escribir sobre este tema para aquellos que, como yo, vienen de otros lenguages orientados a objetos. Hay cosas en Ruby que se hacen de manera distinta y es muy dificil cambiar las costumbres de un día para otro.
Una de las cosas que se hace distinto en Ruby es el tema del polimorfismo. En C++ tenemos las denominadas clases abstractas y en C# o Java los interfaces. Estos elementos de programacion nos facilitan el polimorfismo ya que imponen un contrato a cumplir por la clase de una instancia. De esta maneras y, en tiempo de compilación, se comprueba si un determinado objeto responde a una determinada función, independientemente de su implementación (de ahí el polimorfismo).
En Ruby los interfaces son totalmente inutiles: A cada objeto le basta con responder al metodo buscado para que se le pueda invocar sea del tipo que sea el objeto. Esto se conoce como Duck Typing:
cuando veo un ave que camina como un pato, nada como un pato y se oye como un pato, a esa ave yo la llamo pato.
Es eso polimorfismo? Pues en un grado extremo, en mi opinión, sí lo es.
Pero Ruby tiene una característica muy interesante y es que se puede definir un interfaz e incluirlo en la funcionalidad de la clase consiguiendo no solo que un objeto responda a ciertas llamadas sino que además incluya el comportamiento esperado de esta funcionalidad.
Con esto se conigue dotar a nuestros objetos de un funcionamiento predefinido y, aunque sean objetos de características muy distintas si se les incluyen cierto modulo pasaran a tener un comportamiento similar en dicho modulo. Sirva como ejemplo el siguiente código:
module Herbivore
def eat(meal)
raise "Herbibore only eats Grass" unless meal.kind_of?(Grass)
"#{self.class.name}: I am eating grass"
end
end
module Carnivore
def eat(meal)
raise "Carnivore only eats Cows" unless meal.kind_of?(Cow)
"#{self.class.name}: I am eating a cow"
end
end
module Sleeper
def sleep
"#{self.class.name}: Rest till the next day"
end
end
class Grass
end
class Cow
include Herbivore
include Sleeper
end
class Lion
include Carnivore
include Sleeper
end
c = Cow.new
g = Grass.new
l = Lion.new
puts c.eat(g)
puts l.eat(c)
[c,l].each {|a| puts a.sleep}
Tanto el Lion como la Cow son Sleepers. Con esto, no solo he conseguido hacerles cumplir un interfaz (ambos responden al método sleep) sino que además los dos comparten la misma funcionalidad.
En el caso de ser Carnivore o Herbívore también he conseguido que ambos cumplan un interfaz (respondiendo al método eat), pero su funcionalidad difiere (una Cow no puede comer otra cosas que Grass). Este es la flexibilidad de Ruby: Nada te impide definir interfaces de funcionamiento (comprobados en tiempo de ejecución), como extender dichos interfaces en funcionalidades para compartir comportamientos entre objetos distintos.
Como se puede ver, se convierte en una manera muy natural de programar:
- Una Cow es Hervibore y Sleeper
- Un Lion es Carnivore y Sleeper
- Una Grass no es nada de lo que tengamos definido.
Por cierto, como era de imaginar, la salida del codigo anterior sería algo como lo siguiente:
Cow: I am eating grass Lion: I am eating a cow Cow: Rest till the next day Lion: Rest till the next day
Enviar un comentario nuevo