Menu
Coddy logo textTech

Métodos Abstratos

Parte da seção Programação orientada a objetos do Journey de Ruby da Coddy. Lição 34 de 57.

Uma superclasse pode definir as etapas de um algoritmo e deixar algumas etapas para suas subclasses. Esse é o padrão de método template: render abaixo é fixo, enquanto header e row diferem para cada relatório:

class Report
  def render(items)
    [header, *items.map { |i| row(i) }, footer].join("\n")
  end

  def footer
    "(#{self.class.name})"
  end
end

class CsvReport < Report
  def header
    "name,price"
  end

  def row(item)
    item.join(",")
  end
end

puts CsvReport.new.render([["pen", 2], ["cup", 5]])

Saída:

name,price
pen,2
cup,5
(CsvReport)

A superclasse ainda deve indicar quais métodos uma subclasse precisa escrever. A convenção é um método abstrato que gera NotImplementedError com uma mensagem clara:

class Report
  def render(items)
    [header, *items.map { |i| row(i) }].join("\n")
  end

  def header
    raise NotImplementedError, "#{self.class} must implement header"
  end

  def row(_item)
    raise NotImplementedError, "#{self.class} must implement row"
  end
end

class HtmlReport < Report
  def header
    "[table]"
  end
end

begin
  puts HtmlReport.new.render([1])
rescue NotImplementedError => e
  puts e.message
end

Saída:

HtmlReport must implement row

NotImplementedError não é um StandardError, portanto um rescue sem especificação não o captura. Isso impede que um método ausente seja ignorado pelo tratamento geral de erros; capture-o pelo nome quando precisar:

def risky
  raise NotImplementedError, "missing step"
end

begin
  begin
    risky
  rescue => e
    puts "general handler: #{e.message}"
  end
rescue NotImplementedError => e
  puts "caught by name: #{e.message}"
end
puts NotImplementedError.ancestors.include?(StandardError)

Saída:

caught by name: missing step
false

Além dos métodos abstratos, um template pode oferecer métodos gancho: etapas com um comportamento padrão que as subclasses podem substituir. footer acima é um gancho; assim como um método de sim ou não, como include_total?:

class Report
  def render(items)
    lines = [header, *items.map { |i| row(i) }]
    lines << total_line(items) if include_total?
    lines.join("\n")
  end

  def include_total?     # hook: resposta padrão
    false
  end
end
challenge icon

Desafio

Fácil

Complete as classes de relatório. Report#render(items) retorna a linha de cabeçalho, uma row por item e o footer, unidos por quebras de linha. header e row(item) são abstratos: eles geram NotImplementedError com HtmlReport must implement row (o nome da classe e o método). footer é um gancho que retorna end of report. CsvReport tem o cabeçalho name,qty e linhas pen,3. TextReport tem o cabeçalho NAME QTY, linhas com o nome preenchido até 8 caracteres (pen 3) e o rodapé 2 items. HtmlReport define apenas o cabeçalho [table].

O código fornecido lê o tipo de relatório da primeira linha e os itens como linhas name qty, depois imprime render ou a mensagem de erro.

Seu código deve ficar em report.rb, csv_report.rb, text_report.rb e html_report.rb. main.rb contém o código fornecido de entrada/saída e não pode ser editado.

Experimente você mesmo

require_relative 'report'
require_relative 'csv_report'
require_relative 'text_report'
require_relative 'html_report'

# Código de entrada/saída fornecido: mantenha-o como está
input = []
while (line = gets)
  input << line.chomp
end
kinds = { "csv" => CsvReport, "text" => TextReport, "html" => HtmlReport }
report = kinds[input[0]].new
items = input.drop(1).map(&:split)
begin
  puts report.render(items)
rescue NotImplementedError => e
  puts e.message
end
quiz iconTeste seus conhecimentos

Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.

Todas as lições de Programação orientada a objetos

Pratique por conta própria: Compilador de Ruby online