Delegation with Forwardable
Part of the Object Oriented Programming section of Coddy's Ruby journey. Lesson 46 of 57.
Delegation means an object hands some of its work to an object it holds, instead of inheriting from it. A playlist that has an array of songs is safer than one that is an array, because outside code only gets the methods you choose to forward:
class Playlist
def initialize
@songs = []
end
def add(song)
@songs << song
self
end
def size
@songs.size
end
def first
@songs.first
end
end
list = Playlist.new.add("Intro").add("Dune")
puts list.size
puts list.firstOutput:
2
IntroWriting each forwarding method by hand is repetitive. The standard library's Forwardable does it: extend Forwardable, then def_delegators :@songs, :size, :first forwards those calls to the object in @songs:
require 'forwardable'
class Playlist
extend Forwardable
def_delegators :@songs, :size, :first, :last, :empty?, :include?
def initialize
@songs = []
end
def add(song)
@songs << song
self
end
end
list = Playlist.new.add("Intro").add("Dune")
puts "#{list.size} #{list.first} #{list.last}"
puts list.include?("Dune")
puts list.empty?Output:
2 Intro Dune
true
falsedef_delegator :@songs, :push, :add forwards one method under a new name. The object keeps full control of everything it does not forward, for example delete or clear:
require 'forwardable'
class Queue2
extend Forwardable
def_delegator :@jobs, :push, :enqueue
def_delegator :@jobs, :shift, :next_job
def_delegators :@jobs, :size
def initialize
@jobs = []
end
end
q = Queue2.new
q.enqueue("a")
q.enqueue("b")
puts q.next_job
puts q.size
puts q.respond_to?(:clear)Output:
a
1
falsePrefer delegation ("has a") when a class needs only part of another class's behaviour. Inheriting from Array would expose dozens of methods, such as clear and delete_at, that could break the playlist's rules:
class Playlist < Array # every Array method is public, including clear
end
class Playlist # only what you forward is public
extend Forwardable
def_delegators :@songs, :size, :each
endChallenge
EasyComplete the class Playlist with Forwardable. It holds songs, each a [title, seconds] pair, in @songs. Forward size, empty? and first with def_delegators, and forward push under the name add with def_delegator. Its own method duration returns the total length as m:ss, and titles returns the titles joined by , .
The supplied code adds a song per input line (title,seconds) with add, then prints the size, the first title, the titles, the duration and whether the playlist responds to clear.
Your code goes in playlist.rb. main.rb holds the supplied input/output code and cannot be edited.
Try it yourself
require_relative 'playlist'
# Supplied input/output code: keep it as it is
input = []
while (line = gets)
input << line.chomp
end
list = Playlist.new
input.each do |line|
title, seconds = line.split(",")
list.add([title, seconds.to_i])
end
puts "songs: #{list.size}"
puts "first: #{list.empty? ? "none" : list.first[0]}"
puts "titles: #{list.titles}"
puts "duration: #{list.duration}"
puts "can clear: #{list.respond_to?(:clear)}"
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Classes and Objects
Working With FilesDefining ClassesInstance VariablesMethods and selfto_s and inspectRecap - Library Card7Polymorphism and Duck Typing
PolymorphismDuck TypingAbstract Methodscase and ClassesRecap - Payment Methods2Accessors and Self
Readers and WritersCustom Settersself in MethodsIdentity and EqualityRecap - Contact List8Special Methods
Equality and hashOperator MethodsIndexing and AppendingCustom CollectionsRecap - Fractions11Project: Library Management
Books and MembersBorrowing Books3Class Methods and Variables
Class MethodsClass Instance VariablesConstantsFactory MethodsRecap - Coupon CodesPractice on your own: Online Ruby compiler