Menu
Coddy logo textTech

Делегирование с помощью 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. Он хранит песни, каждая из которых представляет собой пару [title, seconds], в @songs. Передайте методы 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