Menu
Coddy logo textTech

Delegación con Forwardable

Parte de la sección Programación orientada a objetos del Journey de Ruby de Coddy. Lección 46 de 57.

La delegación significa que un objeto entrega parte de su trabajo a un objeto que contiene, en lugar de heredar de él. Una lista de reproducción que tiene un array de canciones es más segura que una que es un array, porque el código externo solo obtiene los métodos que eliges reenviar:

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

Salida:

2
Intro

Escribir cada método de reenvío a mano es repetitivo. La biblioteca estándar Forwardable lo hace: extend Forwardable, después def_delegators :@songs, :size, :first reenvía esas llamadas al objeto en @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?

Salida:

2 Intro Dune
true
false

def_delegator :@songs, :push, :add reenvía un método con un nombre nuevo. El objeto mantiene el control total de todo lo que no reenvía, por ejemplo delete o 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)

Salida:

a
1
false

Prefiere la delegación («tiene un») cuando una clase solo necesita parte del comportamiento de otra clase. Heredar de Array expondría docenas de métodos, como clear y delete_at, que podrían infringir las reglas de la lista de reproducción:

class Playlist < Array      # todos los métodos de Array son públicos, incluyendo clear
end

class Playlist              # solo lo que reenvías es público
  extend Forwardable
  def_delegators :@songs, :size, :each
end
challenge icon

Desafío

Fácil

Completa la clase Playlist con Forwardable. Contiene canciones, cada una un par [title, seconds], en @songs. Reenvía size, empty? y first con def_delegators, y reenvía push con el nombre add mediante def_delegator. Su propio método duration devuelve la duración total como m:ss, y titles devuelve los títulos unidos por , .

El código proporcionado añade una canción por cada línea de entrada (title,seconds) con add, después imprime el tamaño, el primer título, los títulos, la duración y si la lista de reproducción responde a clear.

Tu código va en playlist.rb. main.rb contiene el código proporcionado de entrada/salida y no se puede editar.

Pruébalo tú mismo

require_relative 'playlist'

# Código de entrada/salida suministrado: déjalo tal como está
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 iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Programación orientada a objetos

Practica por tu cuenta: Compilador de Ruby online