# constants
OFF = false; ON = true
# ok, I'm laaaazy, so I type this just once
module Named
attr_accessor :name # getter + setter
def initialize(name)
@name=name
end
end
# gosh, a switch! this one emits a signal labeld :switched
class SignalingSwitch
include Named
include Signaling
def switch
print @name," switched\n"
emit :switched
end
end
# something to switch.. a light!
class Light
include Named
@state=OFF # initial value
attr_accessor :state # getter + setter
def turn(x=!@state) # default: toggle
@state=x
print ' ',@name,' turned ',if @state then "on" else "off" end,"\n"
end
end
# ok, let's roll. two lights
l1 = Light.new("Light1")
l2 = Light.new("Light2")
# each light gets its own switch
s1 = SignalingSwitch.new("Switch1")
connect(s1,:switched,l1,:turn)
s2 = SignalingSwitch.new("Switch2")
connect(s2,:switched,l2,:turn)
So if we call
s1.switch
s1.switch
s2.switch
We get the ouput
Switch1 switched
Light1 turned on
Switch1 switched
Light1 turned off
Switch2 switched
Light2 turned on
Ok, signals and slots are a bit more powerfull. We may add
# add an all of all on switch as well
sOff = SignalingSwitch.new("Switch all off")
connect(sOff,:switched,l1,:turn,OFF)
connect(sOff,:switched,l2,:turn,OFF)
sOn = SignalingSwitch.new("Switch all on")
connect(sOn,:switched,l1,:turn,ON)
connect(sOn,:switched,l2,:turn,ON)
So that via
sOn.switch
sOff.switch
We get the ouput
Switch all on switched
Light1 turned on
Light2 turned on
Switch all off switched
Light1 turned off
Light2 turned off
Ok. So how did I implement it?
# Another Signals + Slots Implementation for Ruby (c) Axel Plinge 2006
# in order to avoid eval(...) cascades, all signaling Objects
# have to 'include Signaling' in order to be able to 'emit'
module Signaling
# connect one of our signals to one someones slot i.e. method
def connect(signal,recipient,slot,*args)
@connections = Hash.new unless @connections
@connections[signal] = [] unless @connections[signal]
@connections[signal].push [recipient.method(slot),args]
end
# emit :signal name => call associated method with args or default value
def emit(name,*args)
return if !@connections
connected_slots =@connections[name]
return if !connected_slots
connected_slots.each do |slot|
slot[0].call(*(slot[1]+args)) # concatenate *args lists
end
end
end
# connect sender's signal to one recipient's slot i.e. method
# called by sender.emit signal,*emit_args
#
# if *args are given, recipient.slot(*args,*emit_args) will be invoked,
# otherwise the just the args from after the emit statement are used
# recipient.slot(*emit_args)
def connect(sender,signal,recipient,slot,*args)
sender.connect(signal,recipient,slot,*args)
end
Modules in Ruby can be used as namespaces in C++ or Java or, as I have done here, for multiple ineritance, just as interfaces are used in Java. Object.method(mehtod_name) gives and Method object with a method call to involke it. Since variable argument lists are arrays, we can concatenate them with + like any other array before unfolding them with *.