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 # すべての Array メソッドは public であり、clear も含む
end
class Playlist # 転送したものだけが public である
extend Forwardable
def_delegators :@songs, :size, :each
endチャレンジ
簡単Forwardable を使ってクラス Playlist を完成させてください。曲を保持し、各曲は [title, seconds] ペアとして @songs に格納されます。def_delegators を使って size、empty?、first を委譲し、def_delegator を使って push を add という名前で委譲してください。独自のメソッド duration は合計の長さを m:ss として返し、titles はタイトルを , で連結して返します。
提供されたコードは、各入力行(title,seconds)につき1曲を 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オンラインコンパイラ