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

(Peter Fitzgibbons) #1

Hello all.

Ok, ok, I'll give more detail on what I'm up to.

In an effort to learn much more Ruby (and more software technique in
general), I'm working through the O'Reilly book "Head First Design
Patterns" (Gang-of-four rewritten).

So, the first "pattern" (not really a pattern officially) is the
Strategy Pattern.
Duck class is copied below.

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.

So, my initial question is: Since ruby has no interfaces, and obeys
duck-typing, is there a more-terse implementation of the below that is
more "The Ruby Way" ?

Much thanks for your sage advice.

---------- duck.rb ----------------
class Duck
  attr :quackDelegate, :flyDelegate

  def performQuack
    @quackDelegate.quack
  end
  
  def performFly
    @flyDelegate.fly
  end
  
  def swim
    "All ducks float, even decoys!"
  end
  
  def setFlyDelegate(fd)
    @flyDelegate = fd
  end
  
end

class FlyWithWings
  def fly
    "I'm flying!"
  end
end

class FlyNoWay
  def fly
    "I can't fly"
  end
end

class FlyRocketPowered
  def fly
    "I'm flying Rocket Powered!!"
  end
end

class Quack
  def quack
    "Quack"
  end
end

class MuteQuack
  def quack
    "<< Silence >>"
  end
end

class Squeak
  def quack
    "Squeak"
  end
end

class MallardDuck < Duck
  def initialize
    @quackDelegate = Quack.new
    @flyDelegate = FlyWithWings.new
  end
end

class ModelDuck < Duck
  def initialize
    @flyDelegate = FlyNoWay.new
    @quackDelegate = Quack.new
  end
End
----------- end duck.rb -----------------

------------ duck_test.rb ---------------
require 'test/unit'
require 'duck'

class DuckTest < Test::Unit::TestCase
  def testMallard
    mallardDuck = MallardDuck.new
    assert_equal "Quack", mallardDuck.performQuack
    assert_equal "I'm flying!", mallardDuck.performFly
  end

  def testModelDuck
    modelDuck = ModelDuck.new
    assert_equal "Quack", modelDuck.performQuack
    assert_equal "I can't fly", modelDuck.performFly
    modelDuck.setFlyDelegate FlyRocketPowered.new
    assert_equal "I'm flying Rocket Powered!!", modelDuck.performFly
  end
  
end
-------------- end duck_test.rb ----------

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

(J-Van) #2

I have no idea if it's appliable in this case, but Ruby's standard
library comes with a Delgate something or other. Might be helpful.

···

On 8/11/05, Peter Fitzgibbons <Peter.Fitzgibbons@lakewoodhomes.net> wrote:

Hello all.

Ok, ok, I'll give more detail on what I'm up to.

In an effort to learn much more Ruby (and more software technique in
general), I'm working through the O'Reilly book "Head First Design
Patterns" (Gang-of-four rewritten).

So, the first "pattern" (not really a pattern officially) is the
Strategy Pattern.
Duck class is copied below.

(Dave Burt) #3

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

(Jim Weirich) #4

Although Peter's demo didn't illustrate it, the key to the strategy pattern is
that the strategy can be dynamically chosen (and changed) at runtime.

However, including modules would be an interesting variation on the Template
Method Pattern, which is similar to Strategy, except that it uses inheritance
to get the variation (and therefore can't change at runtime).

···

On Thursday 11 August 2005 05:21 pm, Dave Burt wrote:

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.

--
-- Jim Weirich jim@weirichhouse.org http://onestepback.org
-----------------------------------------------------------------
"Beware of bugs in the above code; I have only proved it correct,
not tried it." -- Donald Knuth (in a memo to Peter van Emde Boas)

#5

Although Peter's demo didn't illustrate it, the key to the strategy pattern is
that the strategy can be dynamically chosen (and changed) at runtime.

While the solution I gave is able to do that, thanks to Ruby's ease of
reflection, if that's the case, maybe you do want to use delegation
like I did above.

If a Strategy is a single function, I'd probably do it more like this,
though:

Quack = proc do
  puts "Quack!"
end

DaffyQuack = proc do
  puts "Consequences, schmonsequences, as long as I'm rich!"
end

Class Duck
  def initialize() @quacker = Quack end
  def quack(*args, &block) @quacker.call(*args, &block) end
  def attr_writer :quacker
end

daffy = Duck.new
daffy.quack #=> "Quack"
daffy.quacker = DaffyQuack
daffy.quack #=> "Consequences, schmonsequences, as long as I'm rich!"

For multi-function/method strategies, you might consider using
Forwardable to delegate specific methods to modules or objects in
instance variables.

Cheers,
Dave

(Jim Weirich) #6

[... proc based solution elided ...]

Agreed, unless the strategy object requires state. E.g.

  class Employee
    attr_accessor :payment_strategy
    def pay
      @payment_strategy.pay
    end
  end

  class HourlyStrategy
    def initialize(hourly_rate, timecards)
      @rate = hourly_rate
      @timecards = timecards
    end
    def pay
      @timecards.inject(0.0) { |s, tc| s + tc.calculate_pay(@rate) }
    end
  end

  class SalaryStrategy
    def initialize(salary)
      @weekly_salary = salary
    end
    def pay
      @weekly_salary
    end
  end

···

On Thursday 11 August 2005 11:41 pm, dave.burt@gmail.com wrote:

If a Strategy is a single function, I'd probably do it more like this,
though:

--
-- Jim Weirich jim@weirichhouse.org http://onestepback.org
-----------------------------------------------------------------
"Beware of bugs in the above code; I have only proved it correct,
not tried it." -- Donald Knuth (in a memo to Peter van Emde Boas)

(Dave Burt) #7

Jim Weirich wrote:

If a Strategy is a single function, I'd probably do it more like this,
though:

[... proc based solution elided ...]

Agreed, unless the strategy object requires state. E.g.
...
class HourlyStrategy
   def initialize(hourly_rate, timecards)
...

You can put state in procs, too - using local variables rather than instance
variables. (I'm sorry, I'm learning Haskell.)

Of course, the following way is more stupid than Jim's way in the parent
post, which allows not only state, but also private helper methods.

HourlyStrategy = proc do |hourly_rate, timecards|
  proc do
    timecards.inject(0.0) { |s, tc| s + tc.calculate_pay(hourly_rate) }
  end
end

I was going to say that this is more stupid than Jim's way, and his is
definitely more Java-ish, but I kind of like the look of that.

Cheers,
Dave

···

On Thursday 11 August 2005 11:41 pm, dave.burt@gmail.com wrote: