Robert Klemme wrote:

> Some remarks: #freeze might do what you want if you want to prevent any
> change after a certain point in time.

True, except I don't want to prevent *all* changes. (I know the example 
I used gave that impression, but it wasn't a complete example). I only 
want to prevent certain methods from doing useful work 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).

True. It was just the example. In my code I've got a custom 
"DisallowedOperationException" that I throw.

> 
> While using extend might work it's not easy to reverse (if you want to reset
> an instance's state later for example).

Another good point. However, in my code, it is not intended to be 
reversable. Once an object has been fixated, it cannot be "unfixated". 
You're right, though--if it had needed to be reversible, the 
module-based approach might not have been as feasible (unless I had two 
modules, one for each state of the object...which could get ugly).

> 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).

I knew someone would have some great suggestions! :) Thanks, Robert. 
Either of those patterns would have worked as well. The state-based 
approach (which you demonstrated) is especially compelling. Although, I 
think I like the module-based approach because it eliminates the need 
for (1) extra state information (like the @state variable) and (2) 
condition testing (like the 'pre' method), which could become expensive.

I'm all in favor of preconditions, though... your suggestion is one I'll 
certainly be adding to my toolbox. :)

Thanks!

- Jamis

> 
> 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
> 
> 
> .
> 


-- 
Jamis Buck
jgb3 / email.byu.edu
http://www.jamisbuck.org/jamis

"I use octal until I get to 8, and then I switch to decimal."