Menu
CoddyTech

Ủy quyền với Forwardable

Thuộc phần Lập trình hướng đối tượng trong hành trình Ruby của Coddy. Bài học 46 trên 57.

Ủy quyền (delegation) nghĩa là một đối tượng giao một phần công việc cho đối tượng mà nó nắm giữ, thay vì kế thừa từ đối tượng đó. Một danh sách phát có mảng bài hát sẽ an toàn hơn một danh sách phát là mảng, vì code bên ngoài chỉ có thể dùng những phương thức bạn chọn chuyển tiếp:

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

Đầu ra:

2
Intro

Viết từng phương thức chuyển tiếp bằng tay rất lặp lại. Thư viện chuẩn có Forwardable để làm việc đó: extend Forwardable, sau đó def_delegators :@songs, :size, :first chuyển tiếp các lệnh gọi đó đến đối tượng trong @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?

Đầu ra:

2 Intro Dune
true
false

def_delegator :@songs, :push, :add chuyển tiếp một phương thức bằng một tên mới. Đối tượng vẫn toàn quyền kiểm soát mọi thứ mà nó không chuyển tiếp, chẳng hạn như delete hoặc 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)

Đầu ra:

a
1
false

Ưu tiên ủy quyền («có một») khi một lớp chỉ cần một phần hành vi của lớp khác. Kế thừa từ Array sẽ để lộ hàng chục phương thức, chẳng hạn như clear và delete_at, có thể phá vỡ các quy tắc của danh sách phát:

class Playlist < Array      # Mọi phương thức (method) của Array đều là public, kể cả clear
end

class Playlist              # Chỉ những gì bạn chuyển tiếp mới là public
  extend Forwardable
  def_delegators :@songs, :size, :each
end
challenge icon

Thử thách

Dễ

Hoàn thiện lớp Playlist bằng Forwardable. Lớp này lưu các bài hát, mỗi bài là một cặp [title, seconds], trong @songs. Chuyển tiếp các phương thức size, empty? và first bằng def_delegators, rồi chuyển tiếp push dưới tên add bằng def_delegator. Phương thức riêng duration trả về tổng thời lượng theo định dạng m:ss, còn titles trả về các tiêu đề được nối bằng , .

Code được cung cấp thêm một bài hát cho mỗi dòng đầu vào (title,seconds) bằng add, rồi in ra số bài hát, tiêu đề bài đầu tiên, các tiêu đề, thời lượng và thông tin danh sách phát có phản hồi với clear hay không.

Code của bạn nằm trong playlist.rb. main.rb chứa code đầu vào/đầu ra được cung cấp và bạn không thể chỉnh sửa tệp này.

Tự làm thử

require_relative 'playlist'

# Đoạn code đầu vào/đầu ra đã được cung cấp: giữ nguyên
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 iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Trình biên dịch Ruby online