Menu
Coddy logo textTech

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
false

def_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
challenge icon

チャレンジ

簡単

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)}"
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: Rubyオンラインコンパイラ