Menu
Coddy logo textTech

Méthodes abstraites

Fait partie de la section Programmation orientée objet du Journey Ruby de Coddy. Leçon 34 sur 57.

Une superclasse peut définir les étapes d’un algorithme et laisser certaines étapes à ses sous-classes. Il s’agit du modèle template method : render ci-dessous est fixe, tandis que header et row diffèrent selon le rapport :

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

Sortie :

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

La superclasse doit toujours indiquer quelles méthodes une sous-classe doit écrire. La convention consiste à utiliser une méthode abstraite qui déclenche NotImplementedError avec un message clair :

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

Sortie :

HtmlReport must implement row

NotImplementedError n’est pas une StandardError, donc un rescue nu ne l’intercepte pas. Cela empêche qu’une méthode manquante soit ignorée par la gestion générale des erreurs ; interceptez-la par son nom lorsque vous en avez besoin :

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)

Sortie :

caught by name: missing step
false

À côté des méthodes abstraites, un modèle peut proposer des méthodes hook : des étapes avec un comportement par défaut que les sous-classes peuvent redéfinir. footer ci-dessus est un hook ; il en va de même pour une méthode oui/non telle que 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 : réponse par défaut
    false
  end
end
challenge icon

Défi

Facile

Complétez les classes de rapport. Report#render(items) renvoie la ligne d’en-tête, une ligne row par élément et le footer, reliés par des sauts de ligne. header et row(item) sont abstraites : elles lèvent NotImplementedError avec HtmlReport must implement row (le nom de la classe et de la méthode). footer est un point d’extension qui renvoie end of report. CsvReport possède l’en-tête name,qty et des lignes pen,3. TextReport possède l’en-tête NAME QTY, des lignes avec le nom complété jusqu’à 8 caractères (pen 3), et le pied de page 2 items. HtmlReport ne définit que l’en-tête [table].

Le code fourni lit le type de rapport sur la première ligne et les éléments sous forme de lignes name qty, puis affiche render ou le message d’erreur.

Votre code doit être placé dans report.rb, csv_report.rb, text_report.rb et html_report.rb. main.rb contient le code fourni d’entrée/sortie et ne peut pas être modifié.

Essayez vous-même

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

# Code d'entrée/sortie fourni : le garder tel quel
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 iconTestez-vous

Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.

Toutes les leçons de Programmation orientée objet

Entraînez-vous par vous-même : Compilateur Ruby en ligne