抽象メソッド
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 rowNotImplementedError は 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チャレンジ
簡単レポートクラスを完成させてください。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
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
自分で練習してみよう: Rubyオンラインコンパイラ