lib/concurrent-ruby/concurrent/concern/observable.rb
require 'concurrent/collection/copy_on_notify_observer_set' require 'concurrent/collection/copy_on_write_observer_set' module Concurrent module Concern # The [observer pattern](http://en.wikipedia.org/wiki/Observer_pattern) is one # of the most useful design patterns. # # The workflow is very simple: # - an `observer` can register itself to a `subject` via a callback # - many `observers` can be registered to the same `subject` # - the `subject` notifies all registered observers when its status changes # - an `observer` can deregister itself when is no more interested to receive # event notifications # # In a single threaded environment the whole pattern is very easy: the # `subject` can use a simple data structure to manage all its subscribed # `observer`s and every `observer` can react directly to every event without # caring about synchronization. # # In a multi threaded environment things are more complex. The `subject` must # synchronize the access to its data structure and to do so currently we're # using two specialized ObserverSet: {Concurrent::Concern::CopyOnWriteObserverSet} # and {Concurrent::Concern::CopyOnNotifyObserverSet}. # # When implementing and `observer` there's a very important rule to remember: # **there are no guarantees about the thread that will execute the callback** # # Let's take this example # ``` # class Observer # def initialize # @count = 0 # end # # def update # @count += 1 # end # end # # obs = Observer.new # [obj1, obj2, obj3, obj4].each { |o| o.add_observer(obs) } # # execute [obj1, obj2, obj3, obj4] # ``` # # `obs` is wrong because the variable `@count` can be accessed by different # threads at the same time, so it should be synchronized (using either a Mutex # or an AtomicFixum) module Observable # @!macro observable_add_observer # # Adds an observer to this set. If a block is passed, the observer will be # created by this method and no other params should be passed. # # @param [Object] observer the observer to add # @param [Symbol] func the function to call on the observer during notification. # Default is :update # @return [Object] the added observer def add_observer(observer = nil, func = :update, &block) observers.add_observer(observer, func, &block) end # As `#add_observer` but can be used for chaining. # # @param [Object] observer the observer to add # @param [Symbol] func the function to call on the observer during notification. # @return [Observable] self def with_observer(observer = nil, func = :update, &block) add_observer(observer, func, &block) self end # @!macro observable_delete_observer # # Remove `observer` as an observer on this object so that it will no # longer receive notifications. # # @param [Object] observer the observer to remove # @return [Object] the deleted observer def delete_observer(observer) observers.delete_observer(observer) end # @!macro observable_delete_observers # # Remove all observers associated with this object. # # @return [Observable] self def delete_observers observers.delete_observers self end # @!macro observable_count_observers # # Return the number of observers associated with this object. # # @return [Integer] the observers count def count_observers observers.count_observers end protected attr_accessor :observers end end end