0
\$\begingroup\$

I was learning a bit about about observers. Ruby has an Observable module, but I decided to make my own with a few tweaks.

The first important feature is that this module calls one or more observer methods when the observed object's state is changed. When you add an observer, you also have to specify the method that will be called (by using a symbol). You can add the same observer again specifying yet another method (so if you add an observer twice (two different methods), when the object's state changes, both methods will be called).

Ruby's Observable module seems to call the #update method on the observer. I prefer to choose the method to be called, so I did that change.

The freedom to associate multiple methods to the same observer (so it is called multiple times per state change) is something I'm unsure if it is useful in any way, though. What do you think about that?

The second important feature is the ability to choose which instance variables (using symbols) will be passed to the observer when it is notified of a state change. This way, when the observer is notified, it won't have to manually read the object's instance variables, since such variables will be passed as arguments to its method.

module Observable

  def add_observer(observer,method,*instance_variables)
    @observers ||= {}
    @observers[observer] ||= []
    entry = [method,instance_variables]
    @observers[observer] << entry unless @observers[observer].include?(entry)
  end

  def remove_observer(observer)
    return unless @observers
    @observers.delete(observer)
  end

  def state_changed
    return unless @observers
    @observers.each do |observer,entries|
      entries.each do |entry|
        variables = []
        entry[1].each do |symbol|
          variables << instance_variable_get("@#{symbol}")
        end
        observer.send(entry[0],*variables)
      end
    end
  end

  def count_observers
    return @observers.size
  end

  alias_method :changed , :state_changed

end

Example:

observableObject.add_observer(self,:somethingChanged,:instanceVar1,:instanceVar2)

When observableObject's state changes, self's method #somethingChanged will be called. And two instance variables from observableObject (instanceVar1 and instanceVar2) will be passed to the method.

What do you think of this implementation? I like the fact that it seems to be more flexible (being able to choose which method to be called and, optionally, what instance variables to pass).

\$\endgroup\$
1
\$\begingroup\$

A few notes on making this look more like idiomatic Ruby:

  1. There's no need to use return unless @observers in every method. You initialize @observers to a known value, which will never be falsy; there is no harm at all in assuming it will still be set later.

  2. Any time you initialize an empty array, iterate a collection and (for each item) append something to the array, you're missing out on a map.

    Instead of this...

    variables = []
    entry[1].each do |symbol|
      variables << instance_variable_get("@#{symbol}")
    end
    observer.send(entry[0],*variables)
    

    ...use this:

    variables = entry[1].map { |symbol| instance_variable_get("@#{symbol}") }
    observer.send(entry[0],*variables)
    
  3. The last statement in a method is the return value; get rid of your explicit returns:

    def count_observers
      @observers.size
    end
    
\$\endgroup\$
  • \$\begingroup\$ For number 1, if I remove the return in the "state_changed" method, I would get an 'undefined .each method' error if the subject never had observers (because @observers would be nil). \$\endgroup\$ – Omega Jul 12 '13 at 4:09
  • \$\begingroup\$ You're right, for some reason I was reading the add_observer method as the constructor and assuming @observers would always be set. \$\endgroup\$ – meagar Jul 12 '13 at 10:54
0
\$\begingroup\$

I'm a bit late, but I just stumbled on this now. I'm also sorry if I risk ruining the fun by questioning the usefulness.

I first would like to point out that from ruby 1.9.1 you can also choose the function that will be called by passing it to add_observer.

As for passing instance variables, I personally prefer to keep it simple by passing self so that the external observers can use the object interface as usual to come and get the information they need. Basically it keeps things a little bit more decoupled. You wouldn't have a very specific signature in your observable object for a specific listener, just general infrastructure allowing outside code to obtain certain information.

One thing the observer module from the standard library does not provide however is the possibility to have more specific events than just "I've changed". A "standard" method of solving this does exists though, it's called signals and slots. They exist in QT and Boost for c++. Signals and slots have already been ported to ruby by both QtRuby and sigslot.

All in all, I don't find it worth manually implementing this given what already exists...

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.