Menu
Coddy logo textTech

Metody abstrakcyjne

Część sekcji Programowanie obiektowe ścieżki Ruby w Coddy. Lekcja 34 z 57.

Klasa nadrzędna może określać kroki algorytmu i pozostawiać niektóre z nich swoim klasom podrzędnym. To wzorzec metody szablonowej: poniższa metoda render jest stała, natomiast header i row różnią się w zależności od raportu:

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]])

Wynik:

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

Nadklasa nadal powinna wskazywać, które metody podklasa musi napisać. Konwencją jest metoda abstrakcyjna, która zgłasza NotImplementedError z jasnym komunikatem:

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

Wynik:

HtmlReport must implement row

NotImplementedError nie jest błędem typu StandardError, więc samo rescue go nie przechwytuje. Dzięki temu brakująca metoda nie zostanie pominięta przez ogólną obsługę błędów; w razie potrzeby przechwyć ten błąd, podając jego nazwę:

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)

Wynik:

caught by name: missing step
false

Oprócz metod abstrakcyjnych szablon może udostępniać metody hakowe: kroki z domyślną implementacją, które podklasy mogą nadpisywać. Powyższe footer jest metodą hakową; podobnie jak metoda zwracająca odpowiedź tak lub nie, taka jak 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?     # punkt rozszerzenia: domyślna odpowiedź
    false
  end
end
challenge icon

Wyzwanie

Łatwy

Uzupełnij klasy raportów. Report#render(items) zwraca wiersz nagłówka, jeden row dla każdego elementu oraz footer, połączone znakami nowej linii. header i row(item) są abstrakcyjne: zgłaszają NotImplementedError z HtmlReport must implement row (nazwą klasy i metody). footer to metoda haka, która zwraca end of report. CsvReport ma nagłówek name,qty i wiersze pen,3. TextReport ma nagłówek NAME QTY, wiersze z nazwą dopełnioną spacjami do 8 znaków (pen 3) oraz stopkę 2 items. HtmlReport definiuje tylko nagłówek [table].

Dostarczony kod odczytuje rodzaj raportu z pierwszego wiersza, a elementy z wierszy w formacie name qty, a następnie wyświetla wynik render lub komunikat o błędzie.

Twój kod umieść w plikach report.rb, csv_report.rb, text_report.rb i html_report.rb. main.rb zawiera dostarczony kod wejścia/wyjścia i nie można go edytować.

Spróbuj swoich sił

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

# Dostarczony kod wejścia/wyjścia: pozostaw go bez zmian
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 iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator Ruby online