Forum: Ruby The Duck Problem, or accessing one instance var from another

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
6d07e6d95a43357254698ce9723350e6?d=identicon&s=25 Vladimir Agafonkin (mourner)
on 2006-01-24 18:08
(Received via mailing list)
Hi!

What is the best way to access class' instance variables from a method
of another instance variable of the same class that is a class itself?
:-)

OK, Let's say I have Duck class with an instance variable
@quack_behaviour of QuackBehaviour class inside. Duck#quack method
calls one of the QuackBehaviour methods, and I want to access some of
the intance variables (say, @name) of the caller Duck object from that
method.

One way is to set an attr_accessor :name (or use instance_variable_get)
and pass "self" as a parameter to the @quack_behaviour method. But it
seems for me that it is not the most appropriate way of doing this. Or
is it?
Cb48ca5059faf7409a5ab3745a964696?d=identicon&s=25 unknown (Guest)
on 2006-01-24 18:11
(Received via mailing list)
On Mon, 23 Jan 2006, Vladimir Agafonkin wrote:

> me that it is not the most appropriate way of doing this. Or is it?
harp:~ > cat a.rb
   class Duck
     attr_accessor "quack_behaviour"
     def initialize(quack_behaviour) self.quack_behaviour =
quack_behaviour::new(self) end
     def quack() quack_behaviour.quack end
     def name() "duck" end
   end
   class QuackBehaviour
     def self::duck_attr a
       module_eval <<-code
         def #{ a }() duck.#{ a } end
         def #{ a }=(val) duck.#{ a }=val end
       code
     end
     attr_accessor "duck"
     duck_attr "name"
     def initialize(duck) self.duck = duck end
     def quack() p name end
   end

   duck = Duck::new(QuackBehaviour)
   duck.quack

   harp:~ > ruby a.rb
   "duck"

is one way.  if you need to dynamically change the quack_behaviour
object i'd
make a method that pushed/popped/ensured a QuackBehaviour object was in
effect
- but having the QuackBehaviour object have a reference to their parent,
if
possible, simplifies things.

regards.

-a
5befe95e6648daec3dd5728cd36602d0?d=identicon&s=25 Robert Klemme (Guest)
on 2006-01-24 18:17
(Received via mailing list)
Vladimir Agafonkin wrote:
> method.
>
> One way is to set an attr_accessor :name (or use
> instance_variable_get) and pass "self" as a parameter to the
> @quack_behaviour method. But it seems for me that it is not the most
> appropriate way of doing this. Or is it?

Sounds like an application of state or strategy pattern.  I posted some
example code a few days / weeks ago.  Basically, if your two classes are
rather tightly coupled you can have each instance point to the other.
That way both can access methods on the other instance.  The only ugly
thing being that you have to manually keep references in sync.

HTH

    robert
31ab75f7ddda241830659630746cdd3a?d=identicon&s=25 Austin Ziegler (Guest)
on 2006-01-24 18:45
(Received via mailing list)
On 24/01/06, Vladimir Agafonkin <agafonkin@gmail.com> wrote:
> One way is to set an attr_accessor :name (or use
> instance_variable_get) and pass "self" as a parameter to the
> @quack_behaviour method. But it seems for me that it is not the most
> appropriate way of doing this. Or is it?

Huh?

  class QuackBehaviour
    def loudly
	  "LOUDLY"
	end
  end

  class Duck
    def initialize
	  @quack_behaviour = QuackBehaviour.new
	  @name = "Daffy"
	end

    def quack
	  @quack_behaviour.loudly
	end
  end

There's nothing in the above that even remotely allows for
QuackBehaviour#loudly to reach inside of the Duck instance. You will, as
you said, need to either register the duck instance or pass +self+ as a
parameter.

-austin
6d07e6d95a43357254698ce9723350e6?d=identicon&s=25 Vladimir Agafonkin (mourner)
on 2006-01-24 18:51
(Received via mailing list)
Thanks! I will keep that in mind. Though I think that it complicates
things a lot in exchange for using just "attr" instead of, say,
"@obj.attr" for accessign an attribute.
6d07e6d95a43357254698ce9723350e6?d=identicon&s=25 Vladimir Agafonkin (mourner)
on 2006-01-24 19:09
(Received via mailing list)
Thanks, Robert. You're right, a strategy pattern (I just started
reading "Head First Design Patterns" of O'Reilly). What do you mean by
manually keep references synchronized?

I implented my solution with cross-references, and it seems quite
automated (though there's still room for improvement). Here's what I
came with (in short):

#================================================
class Duck
  attr_reader :name

  def initialize(name, fly_behaviour=:fly_with_wings,
                       quack_behaviour=:quack)
    @fly_behaviour = FlyBehaviour.new(self, fly_behaviour)
    @quack_behaviour = QuackBehaviour.new(self, quack_behaviour)
    @name = name
  end

  def fly
    @fly_behaviour.perform
  end

  def quack
    @quack_behaviour.perform
  end
end

class Behaviour
  def initialize(object, behaviour);
    @obj = object;
    @behaviour = behaviour;
  end

  def perform
    send(@behaviour)
  end

  def self.list;
    self.protected_instance_methods
  end
end

class FlyBehaviour < Behaviour
  protected
  def fly_with_wings
    puts "#{@obj.name} is flying!"
  end

  def do_not_fly
    puts "#{@obj.name} can't fly. :-("
  end
end

class QuackBehaviour < Behaviour
  protected
  def quack
    puts "#{@obj.name} is quacking! Quack!!"
  end

  def squeak
    puts "#{@obj.name} is quacking! Squeak!!!"
  end
end

class RubberDuck < Duck
  def initialize(name)
    super(name,:do_not_fly,:squeak)
  end
end

ducks = []
ducks << Duck.new("George")
ducks << RubberDuck.new("Wacky")

ducks.each do |duck|
  duck.quack
  duck.fly
end

p QuackBehaviour.list
#================================================

The next big thing would be to implement generating behaviour-related
methods in the Duck class on the fly. Through method_missing, maybe?
And, of course, behaviour instance variables too. That seems a bit
harder to do, but I'm a programming newbie after all! :-) The perfect
use of the solution would look like:

usually_able_to :fly, :quack, :swim

that looks similiar to Rails has_many and belongs_too. Maybe I need to
check it's code for an idea.

And there's another question: how to make all methods defined in
Behaviour children protected? The code would be more attractive if I
hadn't to write "protected" in the beginning of each behaviour child.
6d07e6d95a43357254698ce9723350e6?d=identicon&s=25 Vladimir Agafonkin (mourner)
on 2006-01-24 19:09
(Received via mailing list)
Thanks, Robert. You're right, a strategy pattern (I just started
reading "Head First Design Patterns" of O'Reilly). What do you mean by
manually keep references synchronized?

I implented my solution with cross-references, and it seems quite
automated (though there's still room for improvement). Here's what I
came with (in short):

#================================================
class Duck
  attr_reader :name

  def initialize(name, fly_behaviour=:fly_with_wings,
                       quack_behaviour=:quack)
    @fly_behaviour = FlyBehaviour.new(self, fly_behaviour)
    @quack_behaviour = QuackBehaviour.new(self, quack_behaviour)
    @name = name
  end

  def fly
    @fly_behaviour.perform
  end

  def quack
    @quack_behaviour.perform
  end
end

class Behaviour
  def initialize(object, behaviour);
    @obj = object;
    @behaviour = behaviour;
  end

  def perform
    send(@behaviour)
  end

  def self.list;
    self.protected_instance_methods
  end
end

class FlyBehaviour < Behaviour
  protected
  def fly_with_wings
    puts "#{@obj.name} is flying!"
  end

  def do_not_fly
    puts "#{@obj.name} can't fly. :-("
  end
end

class QuackBehaviour < Behaviour
  protected
  def quack
    puts "#{@obj.name} is quacking! Quack!!"
  end

  def squeak
    puts "#{@obj.name} is quacking! Squeak!!!"
  end
end

class RubberDuck < Duck
  def initialize(name)
    super(name,:do_not_fly,:squeak)
  end
end

ducks = []
ducks << Duck.new("George")
ducks << RubberDuck.new("Wacky")

ducks.each do |duck|
  duck.quack
  duck.fly
end

p QuackBehaviour.list
#================================================

The next big thing would be to implement generating behaviour-related
methods in the Duck class on the fly. Through method_missing, maybe?
And, of course, behaviour instance variables too. That seems a bit
harder to do, but I'm a programming newbie after all! :-) The perfect
use of the solution would look like:

usually_able_to :fly, :quack, :swim

that looks similiar to Rails has_many and belongs_too. Maybe I need to
check it's code for an idea.

And there's another question: how to make all methods defined in
Behaviour children protected? The code would be more attractive if I
hadn't to write "protected" in the beginning of each behaviour child.
47b1910084592eb77a032bc7d8d1a84e?d=identicon&s=25 Joel VanderWerf (Guest)
on 2006-01-24 20:10
(Received via mailing list)
Vladimir Agafonkin wrote:
> method.
>
> One way is to set an attr_accessor :name (or use instance_variable_get)
> and pass "self" as a parameter to the @quack_behaviour method. But it
> seems for me that it is not the most appropriate way of doing this. Or
> is it?

This is a slightly different way, based on a soon-to-be-released version
of MinDI (which is a dependency injection framework and hence the
"inject" terminology):

module Injectable
  module Injected
    def method_missing(*args, &block)
      @__injectable__container__ || super
      @__injectable__container__.send(*args, &block)
    rescue NoInjectedMethodError
      super
    end
  end

  def inject_into obj
    obj.extend Injected
    obj.instance_variable_set(:@__injectable__container__, self)
    obj
  end

  def method_missing(m, *rest)
    raise NoInjectedMethodError
  end
end

class Duck
  include Injectable

  def quack; @quack_behavior.quack; end
  def waddle; @waddle_behavior.waddle; end

  def initialize(h)
    @quack_behavior = h[:quack_behavior]
    @waddle_behavior = h[:waddle_behavior]

    inject_into @quack_behavior
    inject_into @waddle_behavior
  end
end

class StandardQuacker
  def quack
    puts "QUACK!"
  end
end

class NoisyWaddler
  def waddle
    quack       # note that this propagates to Duck then to quacker
    puts "<waddle>"
    quack
  end
end

duck = Duck.new(
  :quack_behavior   => StandardQuacker.new,
  :waddle_behavior  => NoisyWaddler.new
)

duck.waddle

__END__

Output:

QUACK!
<waddle>
QUACK!
This topic is locked and can not be replied to.