Rubyによるデザインパターン(2)
本記事は初級エンジニアがRubyを使用したデザインパターンをアウトプットしたものになります。また、デザインパターンは種類が多いため、何回かに分けて掲載していきたいと思います。今回はTemplateMethodパターンとFactoryMethodパターンをご紹介します。なお、こちら記事は次のサイトを参考にしております。
TECHSCORE
酒と涙とRubyとRailsと
TemplateMethodパターン
TemplateMethod パターンは、テンプレートの機能を持つパターンです。親クラスで処理の枠組み(テンプレート)を定め、子クラスでその具体的内容を実装します。親クラスで抽象度の高いロジックを組み込み、子クラスで抽象度の低い詳細なロジックを組み込みことで、役割を分担することができます。
サンプルソース
・Report(抽象的なベースの親クラス): レポートを出力する
・HTMLReport(子クラス): HTMLフォーマットでレポートを出力
・PlaneTextReport(子クラス): PlanTextフォーマットでレポートを出力
レポートの出力を行うReportクラスは次のとおり
(Reportクラスは4つのメソッドを持つ)
class Report
def initialize
@title = "html report title"
@text = ["report line 1", "report line 2", "report line 3"]
end
# レポートを出力する順番を規程
def output_report
output_start
output_body
output_end
end
# レポートの先頭で出力
def output_start
end
# レポートの本文で出力
def output_body
@text.each do |line|
output_line(line)
end
end
# output_bodyの内容
# 今回は個別クラスに規定するメソッドとする。規定されてなければエラーを返す
def output_line(line)
raise 'Called abstract method !!'
end
# レポートの末尾で出力
def output_end
end
end
HTML形式でのレポート出力を行うHTMLReportは以下のとおり
(ReportクラスのメソッドでHTML出力する際に変化する3つのメソッドを持つ)
# HTML形式でのレポート出力を行う
class HTMLReport < Report
# レポートの先頭を出力
def output_start
puts ""
end
# output_bodyの内容
def output_line(line)
puts "#{line}"
end
# レポートの末尾を出力
def output_end
puts ""
end
end
*最後にPlaneText形式(で囲う)での出力を行うPlaneTextReportクラスは以下のとおり
(ReportクラスのメソッドでPlaneText形式で出力する際に変化する2つのメソッドを持つ)
# PlaneText形式(*****で囲う)でレポートを出力
class PlaneTextReport < Report
# レポートの先頭で出力
def output_start
puts "**** #{@title} ****"
end
# output_bodyの内容
def output_line(line)
puts line
end
end
以上のコードを実際に実行すると次のようになります。
html_report = HTMLReport.new
html_report.output_report
#
# report line 1
# report line 2
# report line 3
#
plane_text_report = PlaneTextReport.new
plane_text_report.output_report
#**** html report title ****
#report line 1
#report line 2
#report line 3
結果から抽象度の高いメソッドを持つReportクラスから、HTML形式とPlaneText形式でレポートを出力することができました。なお、オブジェクトを初期化するメソッドinitializeは既存のinitializeをオーバーライドして、自由に初期化処理を行うことができます。つまりinitializeは広義でのTempleteMethodパターンとなります。続いて、FactoryMethodパターンを説明します。
FactoryMethodパターン
FactoryMethodパターンはオブジェクトの生成方法に一工夫加えることで、より柔軟なオブジェクトを生成することを目的としています。FactoryMethodパターンではインスタンスの生成を子クラスに行わせることでより柔軟に生成するインスタンスを選択することができるようになります。
それではFactoryMethodをコードを使用して説明していきます。ここではデスクトップパソコンとパソコンを製造する工場を例にします。デスクトップパソコンを表すDesktopクラスは、電源を入れる(start)メソッドを持っており、パソコン工場を表すPersonalComputerFactoryクラスはコンストラクタの引数でパソコンの数を受け取り、またパソコンを出荷するメソッド「ship_out」を持っています。
DesktopクラスとPersonalComputerFactoryクラスは次のとおり
# デスクトップ (Product)
class Desktop
def initialize(name)
@name = name
end
def start
puts "デスクトップ #{@name} は電源を入れました"
end
end
# パソコン工場 (Creator)
class PersonalComputerFactory
def initialize(number_desktops)
@desktops = []
number_desktops.times do |i|
desktop = Desktop.new("デスクトップ #{i}")
@desktops << desktop
end
end
# パソコンを出荷する
def ship_out
@tmp = @desktops.dup
@desktops = []
@tmp
end
end
上のプログラムを実行
factory = PersonalComputerFactory.new(3)
desktops = factory.ship_out
desktops.each { |desktop| desktop.start }
#=> デスクトップ 0 は電源を入れました
#=> デスクトップ 1 は電源を入れました
#=> デスクトップ 2 は電源を入れました
ここで、新たにラップトップ(Laptop)モデルを追加してみます
(インタフェースはデスクトップとまったく同じもの)
# ラップトップ (Product)
class Laptop
def initialize(name)
@name = name
end
def start
puts "ラップトップ #{@name} は電源を入れました"
end
end
先ほど作ったPersonalComputerFactoryモデルをもう一度確認
# パソコン工場 (Creator)
class PersonalComputerFactory
def initialize(number_desktops)
@desktops = []
number_desktops.times do |i|
desktop = Desktop.new("デスクトップ #{i}")
@desktops << desktop
end
end
# パソコンを出荷する
def ship_out
tmp = @desktops.dup
@desktops = []
tmp
end
end
ラップトップモデルを追加する場合、一点問題が発生します。それはPersnalComputerFactoryクラスにおいて、コンストラクタ(initialize)でデスクトップを作っている点です(desktop = Desktop.new("デスクトップ #{i}"))。そこで、PersonalComputerFactoryクラスのデスクトップを生成している部分を子クラス(DesktopFacotory)で実装し、再度、ラップトップを生成するLaptopFactoryクラスを作成してみましょう。
class PersonalComputerFactory
def initialize(number_personal_computers)
@computers = []
number_personal_computers.times do |i|
computer = new_computer("パソコン #{i}")
@computers << computer
end
end
# パソコンを出荷する
def ship_out
@tmp = @computers.dup
@computers = []
@tmp
end
end
# DesktopFactory: デスクトップを生成する (ConcreteCreator)
class DesktopFactory < PersonalComputerFactory
def new_computer(name)
Desktop.new(name)
end
end
# LaptopFactory: ラップトップを生成する (ConcreteCreator)
class LaptopFactory < PersonalComputerFactory
def new_computer(name)
Laptop.new(name)
end
end
上記のPersonalComputerFactoryではnew_computer(パソコンの生成)で処理を抽象化することで、追加されたラップトップモデルに対応することができます。それでは実際にプログラムを実行し、結果を確認してみましょう。
factory = DesktopFactory.new(3)
desktops = factory.ship_out
desktops.each { |desktop| desktop.start }
#=> デスクトップ パソコン 0 は電源を入れました
#=> デスクトップ パソコン 1 は電源を入れました
#=> デスクトップ パソコン 2 は電源を入れました
factory = LaptopFactory.new(3)
laptops = factory.ship_out
laptops.each { |laptop| laptop.start }
#=> ラップトップ パソコン 0 は電源を入れました
#=> ラップトップ パソコン 1 は電源を入れました
#=> ラップトップ パソコン 2 は電源を入れました
結果のとおり、デスクトップ/ラップトップの作成及び各パソコンの電源を入れることができるようになりました。このようにクラスの選択を子クラスに任せることを「FactoryMethod」と呼びます。
なお、ファクトリメソッドは次の3つで構成されています。
・Creator: ConcreteFactoryの共通部分の処理を行う(PersonalComputerFactory)
・ConcreteCreator: 実際にオブジェクトの生成を行う(DesktopFactory, LaptopFactory)
・Product: ConcreteFactoryによって生成される側のオブジェクト(desktop、laptop)
以上で今回の説明を終了します。