Sunday, September 30, 2012

Favor Composition over Inheritance - Part 4

Previously in the programming series posts, I show how to use module to implement composition. One limitation of using module is, duck can not change behavior at run time. Such as MallardDuck can only fly with wing. How can we allow an instance of duck to change their behavior at run time? An answer is using object instead of module.

 To support this feature, the base class, now, hold all the behaviors  implemented for subclass. That quite different from using module, which the base class know nothing about subclass behaviors at all.
class Duck
  attr_reader :name
  attr_accessor :fly_behavior, :quack_behavior
  def initialize
    @name = "mallard duck"
    @fly_behavior = nil
    @quack_behavior = nil
  end

  def fly
    fly_behavior.fly
  end

  def quack
    quack_behavior.quack
  end
end

In this case, the base class by default has no behaviors. It's the subclass job to create, and assign default behaviors of the subclass when initiate an instance. For example, MallardDuck default behavior for flying is fly with wing, etc.
require './duck'
require './behavior/fly_with_wing'
require './behavior/quack_loud'

class MallardDuck < Duck
  def initialize
    @name = "mallard duck"
    @fly_behavior = Behavior::FlyWithWing.new
    @quack_behavior = Behavior::QuackLoud.new
  end
end



For the behaviors, instead of module, it's become a class.
module Behavior
  class FlyWithWing
    def fly
      puts 'Fly with wing.'
    end
  end
end

Here an implementation of duck_app.rb. This example show an instance of MallardDuck, which first can fly and then the same duck can not fly any more when the behavior re-assigned. It still quack loadly thou.
require './mallard_duck'
require './behavior/not_fly'

mduck = MallardDuck.new
p mduck.name
mduck.fly

mduck.fly_behavior = Behavior::NotFly.new
mduck.fly
mduck.quack

Here you have it! A duck that can change behavior at run time.

This implementation of composition is quite similar to java "interface" implementation using in the head first design pattern book [1]. To be almost exactly the same, we would duplicate fly, and quack method all over the sub-classes. That is some how a give and a curse of ruby!

I still one more to go on this topic, a comparison for module and object approaches. Stay tuned!

[1] Head First Design Patterns, Eric Freema, Elisabeth Freeman, O'Reilly 2004.