Menu
Coddy logo textTech

抽象メソッド

CoddyのRubyジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 34/57。

スーパークラスはアルゴリズムの手順を定義し、一部の手順をサブクラスに任せることができます。これがテンプレートメソッドパターンです。以下では、renderは固定されていますが、headerとrowはレポートごとに異なります。

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

出力:

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

スーパークラスは、サブクラスがどのメソッドを記述しなければならないかを引き続き示すべきです。その慣習として、明確なメッセージとともに 抽象メソッド が NotImplementedError を発生させます。

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

出力:

HtmlReport must implement row

NotImplementedError は StandardError ではないため、裸の rescue では捕捉できません。これにより、メソッドの欠落が一般的なエラー処理に握りつぶされるのを防げます。必要な場合は、名前を指定して捕捉します:

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)

出力:

caught by name: missing step
false

抽象メソッドに加えて、テンプレートはフックメソッドを提供できます。これは、サブクラスがオーバーライドできるデフォルト実装付きのステップです。上記のfooterはフックです。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: デフォルトの答え
    false
  end
end
challenge icon

チャレンジ

簡単

レポートクラスを完成させてください。Report#render(items) は、ヘッダー行、各アイテムにつき 1 つの row、そして footer を改行で結合して返します。header と row(item) は抽象メソッドであり、HtmlReport must implement row(クラス名とメソッド)という NotImplementedError を発生させます。footer はフックで、end of report を返します。CsvReport のヘッダーは name,qty で、行は pen,3 です。TextReport のヘッダーは NAME QTY で、名前を 8 文字になるように埋めた行(pen 3)と、フッター 2 items を持ちます。HtmlReport はヘッダー [table] のみを定義します。

提供されたコードは、1 行目からレポートの種類を読み取り、name qty 形式の行としてアイテムを読み取った後、render の結果またはエラーメッセージを出力します。

コードは report.rb、csv_report.rb、text_report.rb、html_report.rb に記述します。main.rb には提供された入出力コードが含まれており、編集できません。

自分で試してみよう

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

# 提供された入出力コード: そのままにしておく
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 icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: Rubyオンラインコンパイラ