Composition over Inheritance

with Ruby

Inheritance

According to Wikipedia:

 

"A way to reuse code of existing objects, or to establish a subtype from an existing object, or both"

Composition

According to Wikipedia:

 

"A technique by which classes may achieve polymorphic behavior and code reuse by containing other classes that implement the desired functionality instead of through inheritance."

Let's code

class Song
    
  ANIMALS = [:pava, :gata, :chiva, :mona, :lora, :perra, :burra]
  def initialize
    @data_song = {}
    ANIMALS.each do |animal|
        mother = animal.to_s
        child = mother[0..-2] + "ito"
        @data_song[animal] =  ["compré una #{mother}, y la #{mother} tuvo un #{child}", 
                               "Tengo la #{mother}, tengo el #{child}"]
    end
  end

  def animals
    ANIMALS
  end

  def recite
    line(ANIMALS.count)
  end

  def line(number)
    "Con real y medio #{phrase(number).join(' ')} y siempre tengo mi real y medio "
  end

  def phrase(number)
    animals.first(number).reverse.map.with_index do |animal, index|
      index == 0 ? @data_song[animal].join(' ') : @data_song[animal][1]
    end
  end

end

Our base class

Base class

1st Feature

RandomSong

without if statements

Inheritance!!!!

class RandomSong < Song 
  def animals
    super.shuffle
  end
end

RandomSong

RandomSong

2nd Feature

EchoSong

without if statements 

Inheritance!!!!

Yes... Again!!

EchoSong

class EchoSong < Song
  def phrase(number)
    super.zip(super).flatten
  end
end

EchoSong

Last Minute Feature

EchoSong + RandomSong

EchoRandomSong

Inheritance??

class Song
    #Crappy Code
end

class RandomSong < Song
    def animals
        super.shuffle
    end
end

class EchoRandomSong < RandomSong
    def phrase(number)
        super.zip(super).flatten
    end
end

EchoSong + RandomSong

class Song
    #Crappy Code
end

class EchoSong < Song
    def phrase(number)
        super.zip(super).flatten
    end
end

class EchoRandomSong < EchoSong
    def animals
        super.shuffle
    end
end

EchoSong + RandomSong

Make a new Subclass with all the code!!!!!

class Song
    #Crappy Code
end

class EchoRandomSong < Song
    def phrase(number)
        super.zip(super).flatten
    end
    
    def animals
        super.shuffle
    end
end

EchoSong + RandomSong

Duplicate all the code???

Really?

NO!!

WWSMD

Composition!!

Analize the subclasses

RandomSong "is-a" Song?

EchoSong "is-a" Song?

RandomSong is just a role

Orderer role

EchoSong is a role too

Formatter role

Extract the roles and convert them in dependencies

class DefaultOrder
    def order(data)
        data
    end
end


class RandomOrder
    def order(data)
        data.shuffle
    end
end


class Song
    
  ANIMALS = [:pava, :gata, :chiva, :mona, :lora, :perra, :burra]

  def initialize(orderer: DefaultOrder.new )
    @data_song = {} 
    @animals = orderer.order(ANIMALS)
    @animals.each do |animal|
        mother = animal.to_s
        child = mother[0..-2] + "ito"
        @data_song[animal] =  ["compré una #{mother}, y la #{mother} tuvo un #{child}", 
                                "Tengo la #{mother}, tengo el #{child}"]
    end
  end


  def recite
    line(@animals.count)
  end

  def line(number)
    "Con real y medio #{phrase(number).join(' ')} y siempre tengo mi real y medio "
  end

  def phrase(number)
    @animals.first(number).reverse.map.with_index do |animal, index|
      index == 0 ? @data_song[animal].join(' ') : @data_song[animal][1]
    end
  end

end
class EchoFormatter
    def format(phrases)
        phrases.zip(phrases).flatten
    end
end


class DefaultFormatter
    def format(phrases)
        phrases
    end
end


class Song

  ANIMALS = [:pava, :gata, :chiva, :mona, :lora, :perra, :burra]
  def initialize(orderer: DefaultOrder.new, formatter: DefaultFormatter.new)
    @data_song = {}
    @animals = orderer.order(ANIMALS)
    @animals.each do |animal|
        mother = animal.to_s
        child = mother[0..-2] + "ito"
        @data_song[animal] =  ["compré una #{mother}, y la #{mother} tuvo un #{child}", 
                               "Tengo la #{mother}, tengo el #{child}"]
    end
    @formatter = formatter
  end


  def recite
    line(@animals.count)
  end

  def line(number)
    song = @formatter.format(phrase(number))
    "Con real y medio #{song.join(' ')} y siempre tengo mi real y medio "
  end

  def phrase(number)
    @animals.first(number).reverse.map.with_index do |animal, index|
      index == 0 ? @data_song[animal].join(' ') : @data_song[animal][1]
    end
  end

end

Composition

  1. Isolate the thing that varies

  2. Name the concept

  3. Define the role

  4. Inject the player

Inheritance

is for specialization

not for sharing code

Gustavo Giménez

@gonzo_

@gusga

Made with Slides.com