"Jamis Buck" <jgb3 / email.byu.edu> schrieb im Newsbeitrag news:4127FC2B.60608 / email.byu.edu... > Just wanted to share a recent "discovery" of mine. (I'm sure many of you > have already discovered this some time ago, but I've found value in list > members sharing their discoveries, so I figured I'd try and give > something back.) > > I'm polishing Copland, getting it ready for RubyConf (and my "Dependency > Injection in Ruby" presentation), and I needed some way to make certain > operations available during an early stage of an object's lifecycle, but > disallowed at a later stage. I was originally thinking of two options: > > 1) don't do anything about it. Expect undefined behavior if the user > tries do to a disallowed operation. > > 2) use reflection to dynamically remove the methods when they are no > longer acceptable. > > After some thought, a better solution came to me. Create a submodule of > the class called "Fixated". Then, when the object needs to change, call > some "fixate" method of the object, which will extend the object with > the "Fixated" module. The Fixated module then implements those > disallowed methods by simply raising an exception: > > class ServicePoint > def add_pending_interceptor( a ) > ( @pending_interceptors ||= [] ).push a > end > > def fixate! > self.extend Fixated > end > > def fixated? > false > end > > module Fixated > def add_pending_interceptor( *args ) > raise NotImplementedException, > "cannot add pending interceptors to fixated object" > end > > def fixate! > # does nothing > end > > def fixated? > true > end > end > end > > svc = ServicePoint.new > svc.add_pending_interceptor( "mock object" ) > svc.fixate! > > # the next line raises an exception > svc.add_pending_interceptor( "mock object #2" ) > > Anyway, it works wonderfully for me. :) Here's where those of you more > knowledgable point out a much more efficient way. ;) Some remarks: #freeze might do what you want if you want to prevent any change after a certain point in time. I would not use NotImplementedException because that generates the false impression that an undefined method was invoked while really it was a state violation (in Java you'd use IllegalStateException for this). While using extend might work it's not easy to reverse (if you want to reset an instance's state later for example). So I'd rather use either strategy pattern (or state pattern, they are quite similar) or model states with constants (less typing than those patterns but better documentation IMHO because each method states clearly what precondition it has; and might be a bit more runtime overhead). class ServicePoint def initialize @state = :initial end def add_pending_interceptor( a ) pre :initial ( @pending_interceptors ||= [] ).push a end def fixate! @state = :fixed end def fixated? @state == :fixed end private def pre(*states) raise "State error" unless states.include? @state end end ?> svc = ServicePoint.new => #<ServicePoint:0x101985c0 @state=:initial> >> svc.add_pending_interceptor( "mock object" ) => ["mock object"] >> svc.fixate! => :fixed >> ?> # the next line raises an exception ?> svc.add_pending_interceptor( "mock object #2" ) RuntimeError: State error from (irb):41:in `pre' from (irb):45:in `add_pending_interceptor' from (irb):64 from (null):0 From here it's just a small step to using full preconditions: def pre raise "Precondition Error" unless yield end def add_pending_interceptor( a ) pre { @state == :initial } ( @pending_interceptors ||= [] ).push a end Kind regards robert