The Duck Problem, or accessing one instance var from another


#1

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?
:slight_smile:

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?


#2

On Mon, 23 Jan 2006, Vladimir A. 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


#3

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

#4

On 24/01/06, Vladimir A. removed_email_address@domain.invalid 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


#5

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.


#6

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


#7

Vladimir A. 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 “”
quack
end
end

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

duck.waddle

END

Output:

QUACK!

QUACK!