From: Brian Palmer Date: 2004-08-18T10:43:17+09:00 Subject: Observer Pattern -- generalized implementation 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