Fixating an object

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. :slight_smile: Here's where those of you more knowledgable point out a much more efficient way. :wink:

- Jamis

···

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

"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. :slight_smile: Here's where those of you more
knowledgable point out a much more efficient way. :wink:

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

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! :slight_smile: 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. :slight_smile:

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

"Jamis Buck" <jgb3@email.byu.edu> schrieb im Newsbeitrag
news:412893BB.1050506@email.byu.edu...

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.

Yeah, I guessed so. Just wanted to make sure freeze didn't go unnoticed.

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

Ah, I see.

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

And I wouldn't know how to make this work: AFAIK there is no unextend and
once you've extended your instance by all modules, further extension doesn't
change anything:

module ModA; def foo() "foo" end; end

=> nil

module ModB; def foo() "bar" end; end

=> nil

class Foo; def foo() "xxx" end ; end

=> nil

f=Foo.new

=> #<Foo:0x10171fc8>

f.foo

=> "xxx"

f.extend ModA

=> #<Foo:0x10171fc8>

f.foo

=> "foo"

f.extend ModB

=> #<Foo:0x10171fc8>

f.foo

=> "bar"

f.extend ModA

=> #<Foo:0x10171fc8>

f.foo

=> "bar"

> 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! :slight_smile: Thanks, Robert.

You're welcome.

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.

It shares (2) with strategy/state pattern but with the added advantage of
saving a method call (the delegation). In fact, the longer I think about it
the more I like the module approach because it is very similar to the state
pattern but saves the overhead (you named it) - if only there would be a way
to unextend a module. That's really the biggest showstopper for using
mixins to implement state pattern. Darn...

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

I'm glad that I could share some useful thoughts.

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

How do you get to "8" in octal? Is there a new variant "octal plus" that
has other digits than 0,1,2,3,4,5,6,7? :slight_smile:

Kind regards

    robert

Robert Klemme wrote:

"Jamis Buck" <jgb3@email.byu.edu> schrieb im Newsbeitrag
news:412893BB.1050506@email.byu.edu...

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.

It shares (2) with strategy/state pattern but with the added advantage of
saving a method call (the delegation). In fact, the longer I think about it
the more I like the module approach because it is very similar to the state
pattern but saves the overhead (you named it) - if only there would be a way
to unextend a module. That's really the biggest showstopper for using
mixins to implement state pattern. Darn...

Hmm. What about some kind of "toggle!" method, which would dynamically re-alias a method between two different implementations? It's not as nice as mixins, but it gives the same effect, more or less...

Or... can you dynamically create a new module with the desired functionality and extend your object with that? Hmm...

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

How do you get to "8" in octal? Is there a new variant "octal plus" that
has other digits than 0,1,2,3,4,5,6,7? :slight_smile:

:slight_smile: That's the idea. It was a quote of a coworker of mine explaining (facetiously) why he prefers the decimal base to the octal base.

Kind regards

    robert

- Jamis

···

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

"Jamis Buck" <jgb3@email.byu.edu> schrieb im Newsbeitrag
news:412A0746.30200@email.byu.edu...

Robert Klemme wrote:
> "Jamis Buck" <jgb3@email.byu.edu> schrieb im Newsbeitrag
> news:412893BB.1050506@email.byu.edu...
>
>>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.
>
>
> It shares (2) with strategy/state pattern but with the added advantage

of

> saving a method call (the delegation). In fact, the longer I think

about it

> the more I like the module approach because it is very similar to the

state

> pattern but saves the overhead (you named it) - if only there would be a

way

> to unextend a module. That's really the biggest showstopper for using
> mixins to implement state pattern. Darn...

Hmm. What about some kind of "toggle!" method, which would dynamically
re-alias a method between two different implementations? It's not as
nice as mixins, but it gives the same effect, more or less...

That certainly could be made to work - the downside is IMHO that it tripes
your state dependend methods if you have two states: you need the impl for
state 1 and for state 2 plus the aliasing place. That works exactly
opposite to what state pattern tries to achieve, a minimized number of
shorter methods per class.

Or... can you dynamically create a new module with the desired
functionality and extend your object with that? Hmm...

>>"I use octal until I get to 8, and then I switch to decimal."
>
>
> How do you get to "8" in octal? Is there a new variant "octal plus"

that

> has other digits than 0,1,2,3,4,5,6,7? :slight_smile:

:slight_smile: That's the idea. It was a quote of a coworker of mine explaining
(facetiously) why he prefers the decimal base to the octal base.

:-))

Kind regards

    robert