Head First Design Patterns - Strategy Pattern [was: Java/C# "interface" in Ruby ?]

(Peter Fitzgibbons) #1

How do I change the mixed-in method at runtime ?
Also, is the mixin class-level or object-level?

Peter J. Fitzgibbons
Applications Manager
Lakewood Homes - "The American Dream Builder"(r)
Peter.Fitzgibbons@Lakewoodhomes.net
(847) 884-8800

···

-----Original Message-----
From: Dave Burt [mailto:dave@burt.id.au]
Sent: Thursday, August 11, 2005 4:21 PM
To: ruby-talk ML
Subject: Re: Head First Design Patterns - Strategy Pattern [was: Java/C#
"interface" in Ruby ?]

Peter Fitzgibbons wrote:

...
My question is this: The :quackDelegate and :flyDelegate are written
in Java/C# as object instances of an interface. Clearly I've ditched
the interface itself, but the rest is pretty much as described in the

book.

This looks like exactly what mixins are for - you can put the
implementation itself in a module and then include it rather than using
delegation and having extra objects with one-method floating around.

class Duck
  def swim() "All ducks float, even decoys!" end end

module FlyWithWings
  def fly() "I'm flying!" end
end

# other Flys and Quacks as above, the only change is class -> module...

class MallardDuck < Duck
  include Quack
  include FlyWithWings
end

class ModelDuck < Duck
  include FlyNoWay
  include Quack
end

# next file, duck_test.rb
require 'test/unit'
require 'duck'

class DuckTest < Test::Unit::TestCase
  def testMallard
    mallardDuck = MallardDuck.new
    assert_equal "Quack", mallardDuck.quack # no intermediate method
needed
    assert_equal "I'm flying!", mallardDuck.fly
  end

  def testModelDuck
    modelDuck = ModelDuck.new
    assert_equal "Quack", modelDuck.quack
    assert_equal "I can't fly", modelDuck.fly
    modelDuck.extend FlyRocketPowered
    assert_equal "I'm flying Rocket Powered!!", modelDuck.fly
  end

end

And there you have it - much nicer!

If you do want to use delegation (I don't know why), you can use a class
with a single class- (like Java static-) method; then you don't need to
use FlyWithWings.new, you just pass the FlyWithWings class:

class FlyWithWings
  def self.fly() "I'm flying!" end
end
# ... (only changes are "def fly()" -> "def self.fly()") class
MallardDuck < Duck
  def initialize
    @flyDelegate = FlyWithWings
# ... (only changes are "= FlyWithWings.new" -> "= FlyWithWings")

Cheers,
Dave

#2

How do I change the mixed-in method at runtime ?

You could mean a few things by this.
1) You can change a modelDuck to fly using a different strategy as
shown in the test code already given:
modelDuck.fly
  modelDuck.extend FlyRocketPowered
2) You can change all ModelDucks' fly strategy like this:
class ModelDucks
  include FlyRocketPowered
end
or like this:
ModelDucks.instance_eval {include FlyRocketPowered}
3) You can change the implementation of a strategy like this:
module FlyRocketPowered
  def fly() puts "Upgraded rockets!!!" end
end
or like this:
FlyRocketPowered.instance_eval {
  define_method(:fly) { puts "Upgraded rockets!!!" }
}

Also, is the mixin class-level or object-level?

Above, I defined instance (object-) methods in the fly and quack
modules. By including those modules, classes (ducks) gain those
instance methods.

In my delegation example, I defined fly as a method of the FlyWithWings
class itself (call this like so: FlyWithWings.fly).

Cheers,
Dave