🐷
Rubyで学ぶテンプレートメソッドパターン (Template Method Pattern)
1. どんなもの?
テンプレートメソッドパターンは、処理の大まかな流れをスーパークラスで定義し、具体的な処理の詳細をサブクラスに委ねるデザインパターンです。
このパターンを使用することで、共通の処理を再利用しながら、特定の部分だけをカスタマイズできます。
例えば、レポート生成やデータの前処理といった「処理の流れが決まっているが、部分的に異なる実装が必要」な場面で利用されます。
2. 通常の実装方法と比べてどこがすごいの?
通常の方法
各クラスに個別の処理を記述する場合、同じような処理を何度も繰り返し実装する必要があります。
class HTMLReport
def generate
puts "<html>"
puts "<head><title>Report Title</title></head>"
puts "<body>Report content</body>"
puts "</html>"
end
end
class TextReport
def generate
puts "Report Title"
puts "============"
puts "Report content"
end
end
html_report = HTMLReport.new
html_report.generate
text_report = TextReport.new
text_report.generate
-
課題:
- 共通の処理(タイトルやフッター部分など)が重複する。
- 新しい形式のレポートを追加する場合、同じようなコードを繰り返す必要がある。
テンプレートメソッドパターンの利点
テンプレートメソッドパターンを使うと、共通の処理をスーパークラスにまとめ、サブクラスで必要な部分だけをカスタマイズできます。
class Report
def generate
header
content
footer
end
def header
raise NotImplementedError, "This method should be overridden in subclasses"
end
def content
raise NotImplementedError, "This method should be overridden in subclasses"
end
def footer
puts "Generated by Template Method Pattern"
end
end
class HTMLReport < Report
def header
puts "<html><head><title>Report Title</title></head>"
end
def content
puts "<body>Report content in HTML format</body>"
end
def footer
super
puts "</html>"
end
end
class TextReport < Report
def header
puts "Report Title\n============"
end
def content
puts "Report content in plain text"
end
end
html_report = HTMLReport.new
html_report.generate
text_report = TextReport.new
text_report.generate
-
利点:
- 共通の処理をスーパークラスにまとめることで、コードの重複を削減。
- 新しい形式を追加する際も、特定の部分だけを実装すればよい。
3. 技術や手法の"キモ"はどこにある?
-
テンプレートメソッドの定義
- スーパークラスで処理の流れを定義し、詳細はサブクラスに任せます。
-
抽象メソッドの活用
- スーパークラスで抽象メソッド(
raise NotImplementedError
を持つメソッド)を定義し、サブクラスに具体的な実装を強制します。
- スーパークラスで抽象メソッド(
-
拡張性の確保
- 新しいサブクラスを作成するだけで、異なる処理を簡単に追加できます。
4. 実装例
例: ファイルフォーマット変換
異なるフォーマット(JSONやXML)のデータをテンプレートメソッドパターンを利用して処理します。
class FileProcessor
def process(file_path)
data = read_file(file_path)
parsed_data = parse_data(data)
validate_data(parsed_data)
save_data(parsed_data)
puts "File processed successfully!"
end
private
def read_file(file_path)
raise NotImplementedError, "This method should be overridden in subclasses"
end
def parse_data(data)
raise NotImplementedError, "This method should be overridden in subclasses"
end
def validate_data(data)
raise NotImplementedError, "This method should be overridden in subclasses"
end
def save_data(data)
puts "Saving data: #{data.inspect}"
end
end
class JSONProcessor < FileProcessor
require 'json'
private
def read_file(file_path)
puts "Reading JSON file: #{file_path}"
File.read(file_path)
end
def parse_data(data)
puts "Parsing JSON data"
JSON.parse(data)
end
def validate_data(data)
puts "Validating JSON data"
raise "Invalid data" unless data.is_a?(Hash)
end
end
class XMLProcessor < FileProcessor
require 'nokogiri'
private
def read_file(file_path)
puts "Reading XML file: #{file_path}"
File.read(file_path)
end
def parse_data(data)
puts "Parsing XML data"
Nokogiri::XML(data)
end
def validate_data(data)
puts "Validating XML data"
raise "Invalid XML data" unless data.root
end
end
# 実行例
json_processor = JSONProcessor.new
json_processor.process("data.json")
xml_processor = XMLProcessor.new
xml_processor.process("data.xml")
- 実装ポイント
- テンプレートメソッドの定義:
- スーパークラスのprocessメソッドが、ファイル処理の流れをテンプレートとして定義しています。
- サブクラスはread_fileやparse_dataなどの詳細処理を実装。
- 共通処理の再利用:
- save_dataなど、どのフォーマットでも共通な処理はスーパークラスにまとめています。
- 拡張性:
- 新しいフォーマット(例: CSV)を追加する場合、新しいサブクラスを作成するだけで対応可能です。
- テンプレートメソッドの定義:
5. 議論はあるか?
メリット
- 共通の処理をテンプレート化できるため、コードの重複を削減。
- サブクラスを利用することで拡張性が高い。
- 処理の流れを統一できるため、予測可能な動作が保証される。
デメリット
- サブクラスの数が増えると管理が煩雑になる。
- テンプレートメソッドの設計が複雑になる場合、変更が難しくなる。
議論
テンプレートメソッドパターンは、共通処理が多い場面で非常に有効ですが、サブクラスの管理コストや設計の複雑さを考慮する必要があります。
特に、大規模なプロジェクトでは慎重に適用範囲を検討すべきです。
6. まとめ
テンプレートメソッドパターンは、共通の処理をスーパークラスにまとめ、部分的な処理をサブクラスに委譲するデザインパターンです。
このパターンを活用することで、コードの重複を削減し、拡張性を高めることができますが、サブクラスの増加による複雑さを管理する工夫も重要です。
Discussion