B
Brian Palmer
I know you can emulate sending different messages using the 'observer'
standard library, but I've never felt that approach was natural, and I
haven't found a general-purpose Observer Pattern library that fits me,
so I've been working on my own approach (borrowing heavily from C#).
I'm still pretty ruby-nuby, though, so I'm looking for
suggestions/improvements. Anyone have any thoughts?
The library is used like so:
-----------------------------------------
# a hypothetical text box object
class TextBox
sends "text_changed", "key_pressed"
def initialize txt = ""
@txt = txt
end
def txt= txt
@txt = txt
text_changed
end
def key_press key
key_pressed key
end
end
def my_key_press(k)
puts "KEY: #{k}"
end
box = TextBox::new
box.on_text_changed += proc { puts "Text changed!" }
box.on_key_pressed += method
my_key_press)
box.txt= "New text!" # => "Text changed!"
box.key_press 'j' # => "KEY: j"
box.on_key_pressed -= method
my_key_press)
box.key_press 'k' # => Nothing happens
-----------------------------------------
So yeah, very much like C#. You call += and -= to add and remove
observers, respectively. The functions are generated automatically when
you call Module#sends inside a class definition. So you call
sends("text_changed"), and you get on_text_changed and text_changed made
for you. (text_changed should maybe be called notify_text_changed or
similar)
And the (very basic) library:
-----------------------------------------
module Delegate
class Delegate
def initialize
@observers = []
end
def + observer_method
@observers << observer_method
self
end
def - observer_method
@observers.delete observer_method
self
end
def call *the_args
@observers.each { |cb| cb.call(*the_args) }
end
end # class Delegate
class ::Module
def sends *args
args.each { |arg|
class_eval <<-CEEND
def on_#{arg}
@#{arg}_delegate ||= Delegate::new
end
def on_#{arg}= a
self
end
private
def #{arg} *the_args
@#{arg}_delegate ||= Delegate::new
@#{arg}_delegate.call *the_args
end
CEEND
}
end # def sends
end # class ::Module
end # module Delegate
-----------------------------------------
So yeah, the whole "on_#{arg}= a" thing is a hack, but += doesn't behave
as I would expect when used after a method. I would think it would call
the method first, and then call += on its return value. But it calls
the method, calls + on its return value, then calls the method= version
of the method. Not intuitive, at least not for me, but I guess that's
the order of operations. I tried forcing it by putting () after the
method and before +=, but that just caused a syntax error in ruby 1.8.1.
Anybody have any thoughts? How could I improve it/optimize it? C#'s
handling of delegates and events is my favorite part of the languge,
alongside attribute metadata, so I had to have it in ruby. It's also a
huge boost the the readability of GUI code, in my eyes.
-- Brian Palmer
standard library, but I've never felt that approach was natural, and I
haven't found a general-purpose Observer Pattern library that fits me,
so I've been working on my own approach (borrowing heavily from C#).
I'm still pretty ruby-nuby, though, so I'm looking for
suggestions/improvements. Anyone have any thoughts?
The library is used like so:
-----------------------------------------
# a hypothetical text box object
class TextBox
sends "text_changed", "key_pressed"
def initialize txt = ""
@txt = txt
end
def txt= txt
@txt = txt
text_changed
end
def key_press key
key_pressed key
end
end
def my_key_press(k)
puts "KEY: #{k}"
end
box = TextBox::new
box.on_text_changed += proc { puts "Text changed!" }
box.on_key_pressed += method
box.txt= "New text!" # => "Text changed!"
box.key_press 'j' # => "KEY: j"
box.on_key_pressed -= method
box.key_press 'k' # => Nothing happens
-----------------------------------------
So yeah, very much like C#. You call += and -= to add and remove
observers, respectively. The functions are generated automatically when
you call Module#sends inside a class definition. So you call
sends("text_changed"), and you get on_text_changed and text_changed made
for you. (text_changed should maybe be called notify_text_changed or
similar)
And the (very basic) library:
-----------------------------------------
module Delegate
class Delegate
def initialize
@observers = []
end
def + observer_method
@observers << observer_method
self
end
def - observer_method
@observers.delete observer_method
self
end
def call *the_args
@observers.each { |cb| cb.call(*the_args) }
end
end # class Delegate
class ::Module
def sends *args
args.each { |arg|
class_eval <<-CEEND
def on_#{arg}
@#{arg}_delegate ||= Delegate::new
end
def on_#{arg}= a
self
end
private
def #{arg} *the_args
@#{arg}_delegate ||= Delegate::new
@#{arg}_delegate.call *the_args
end
CEEND
}
end # def sends
end # class ::Module
end # module Delegate
-----------------------------------------
So yeah, the whole "on_#{arg}= a" thing is a hack, but += doesn't behave
as I would expect when used after a method. I would think it would call
the method first, and then call += on its return value. But it calls
the method, calls + on its return value, then calls the method= version
of the method. Not intuitive, at least not for me, but I guess that's
the order of operations. I tried forcing it by putting () after the
method and before +=, but that just caused a syntax error in ruby 1.8.1.
Anybody have any thoughts? How could I improve it/optimize it? C#'s
handling of delegates and events is my favorite part of the languge,
alongside attribute metadata, so I had to have it in ruby. It's also a
huge boost the the readability of GUI code, in my eyes.
-- Brian Palmer