A recent discussion in the GOOS’ group has lead me to consider different ways to compose objects in Ruby. Specifically, as module inclusion seems to be the favored approach for adding stuff to classes in Ruby, I’ve became interested in finding a more flexible idiom for this.
The objective, therefore, is to define an instance variable in a module and be able to have it injected in instances of some class. Since I’m not that familiar with Ruby yet, I’m forced to turn to other languages for inspiration. A possible solution in Scala, presented below, is kind of intuitive.
Let’s start defining a simple trait Lightsource
.
trait Lightsource {
def on
def off
}
The goal is to have a instance of a class implementing this trait injected in every instance of the class Room
below.
abstract class Room {
val lightsource:Lightsource
def enter = lightsource.on
def leave = lightsource.off
}
The injection can be performed by mixing-in the Room
class with traits designed to provide a Lightsource
. For example:
trait FluorescentLamp {
val lightsource = new Lightsource {
def on = println("Sad light on")
def off = println("Sad light off")
}
}
The actual composition reads nicely:
val sadRoom = new Room with FluorescentLamp
If the fluorescent lamp is unsatisfactory, an alternative implementation can be provided:
trait IncandescentLamp {
val lightsource = new Lightsource {
def on = println("Warm light on")
def off = println("Warm light off")
}
}
val warmRoom = new Room with IncandescentLamp
And I will stop at this point. Although there is opportunity for improvement, the implementation is satisfactory enough for me.
Having succeeded at the Scala front, we can tackle the same issue using Ruby. Here, we find ourselves both unable and not required to declare interfaces, so we move directly to the definition of our Room
class.
class Room
def enter
@lightsource.on
end
def leave
@lightsource.off
end
end
The next step is to define a module responsible for providing an @lightsource
object to instances of the Room` class. For instance:
module FluorescentLamp
class FluorescentLampImpl
def on
puts "Sad light on"
end
def off
puts "Sad light off"
end
end
def initialize(*args, &b)
super
@lightsource = FluorescentLampImpl.new
end
end
Alternatively:
module IncandescentLamp
class IncandescentLampImpl
def on
puts "Warm light on"
end
def off
puts "Warm light off"
end
end
def initialize(*args, &b)
super
@lightsource = IncandescentLampImpl.new
end
end
The actual composition does not read so nicely, but surely it can be improved by some sort of DSL. Being lazy, I’ll skip that, though:
sadRoom = Class.new(Room) do
include FluorescentLamp
end.new
warmRoom = Class.new(Room) do
include IncandescentLamp
end.new
Now, I’m suppose that this approach is not really within Ruby’s orthodoxy, but I found it interesting nevertheless. Also, it surprised me that the Scala version is both cleaner and smaller than the Ruby version, while still providing the safety advantages of the static typing.