Menu
CoddyTech

Делегирование с помощью Forwardable

Часть раздела Объектно-ориентированное программирование путешествия по Ruby на Coddy. Урок 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 перенаправляет один метод под новым именем. Объект сохраняет полный контроль над всем, что он не перенаправляет, например над 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
challenge icon

Задание

Легко

Дополните класс Playlist с помощью Forwardable. Он хранит песни в @songs, каждая песня — это пара [title, seconds]. Перенаправьте вызовы size, empty? и first с помощью def_delegators, а вызовы push перенаправьте под именем add с помощью def_delegator. Собственный метод 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)}"
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор Ruby