Forwardableによる委譲
CoddyのRubyジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 46/57。
委譲とは、あるオブジェクトが別のオブジェクトを継承するのではなく、そのオブジェクトに仕事の一部を任せることです。曲の配列を持つプレイリストは、配列そのものであるプレイリストより安全です。外部のコードが使えるのは、転送するように選んだメソッドだけだからです。
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.first出力:
2
Intro転送メソッドを一つずつ手書きするのは繰り返しの作業です。標準ライブラリのForwardableを使えば、それを実現できます。extend Forwardableを呼び出し、続いてdef_delegators :@songs, :size, :firstを記述すると、これらの呼び出しは@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?出力:
2 Intro Dune
true
falsedef_delegator :@songs, :push, :add は、1つのメソッドを新しい名前で転送します。転送しないすべての処理(たとえば delete や 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)出力:
a
1
falseクラスが別のクラスの振る舞いの一部だけを必要とする場合は、委譲(「has-a」)を優先しましょう。Arrayを継承すると、プレイリストのルールを壊す可能性のあるclearやdelete_atなど、数十ものメソッドが公開されてしまいます。
class Playlist < Array # clear を含む Array のすべてのメソッドが public になる
end
class Playlist # 委譲したものだけが public になる
extend Forwardable
def_delegators :@songs, :size, :each
endチャレンジ
簡単Forwardableを使ってPlaylistクラスを完成させてください。@songsに曲を保持します。各曲は[title, seconds]のペアです。def_delegatorsでsize、empty?、firstを委譲し、def_delegatorでpushをaddという名前で委譲してください。独自のメソッドdurationは合計時間をm:ss形式で返し、titlesはタイトルを, で連結して返します。
用意されたコードは、入力の各行(title,seconds)についてaddで曲を追加し、その後、曲数、最初のタイトル、タイトル一覧、再生時間、およびプレイリストがclearに応答するかどうかを出力します。
コードはplaylist.rbに記述します。main.rbには用意された入出力コードが含まれており、編集できません。
自分で試してみよう
require_relative 'playlist'
# 提供された入出力コード:そのままにしてください
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)}"
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
自分で練習してみよう: Rubyオンラインコンパイラ