使用 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 会用新名称转发一个方法。对象仍完全控制所有未转发的操作,例如 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如果一个类只需要另一个类的部分行为,优先使用委托(“拥有一个”)。如果继承自 Array,就会暴露几十个方法,例如 clear 和 delete_at,这些方法可能会破坏播放列表的规则:
class Playlist < Array # Array 的所有方法都是公开的,包括 clear
end
class Playlist # 只有你转发的方法是公开的
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)添加一首歌曲,然后打印歌曲数量、第一首歌曲的标题、所有歌曲标题、总时长,以及播放列表是否响应 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 在线编译器