Menu
CoddyTech

抽象メソッド

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では捕捉されません。これにより、メソッドが見つからない場合に一般的なエラー処理で握りつぶされることを防げます。必要な場合は、名前を指定して 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?     # フック: デフォルトの戻り値
    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] のみを定義します。

提供されたコードは、最初の行からレポートの種類を読み取り、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オンラインコンパイラ