GoFによるデザインパターンを会社のみんなで勉強中です。今回prototypeを担当したのでメモがてら残しておきます。
なお、プロジェクトで使用しているのがrubyなので、rubyで書いています。
Introduction
What’s Prototype pattern?
prototypeパターンは、以下のような状況で使うと効果的なプログラミングデザインパターンの一つです。
- 種類が多すぎてクラスにまとめられない
- クラスからインスタンス生成するのが難しい場合
- インスタンス生成を特定のクラスに依存させたくない場合
とった状況を解決するために「インスタンスを複製して新たなインスタンスを生成する」という手法を取る時に有効なのがprototypeパターンです。
What is the difference from the Factory-pattern?
Factoryパターンは、インスタンスの生成をサブクラスに委譲することでインスタンスの生成をシンプルに記述させるためのデザインパターン。
Prototypeパターンは、既に生成したインスタンスのコピーを渡す。すなわち、状態を持ったオブジェクトの生成を容易にするためのデザインパターンという違いがあります。
Case Study
Situation
ここはとある大学のコピー室。
日々、学生や教授が作成したレポートが登録され、それらはコピーして持ち帰ることが可能です。
- レポートをコピー機に登録する
- コピー機は登録されたレポートのコピーを渡すことができる
という機能を実装する必要がありそうです。
( 現実の案件などでも同様に、ユーザーが登録したオブジェクトをコピーして使うというシチュエーションなどが考えられます。)
Class DIagram

Codes
Interface
# Interfaceそのものの振る舞いを定義するmodule
module BasicInterface
def self.check_methods(klass, method_symbols)
puts "Cheking #{klass} class..."
method_symbols.each do |symbol|
if klass.instance_methods.grep(symbol).length == 0
puts "you must implement '#{symbol}'"
raise NoMethodError
end
end
puts "#{klass} has included #{self}!"
end
end
# 今回用意したInterface
module DocumentInterface
include BasicInterface
def self.included(klass)
must_define_methods = [:title, :author, :body]
BasicInterface.check_methods(klass, must_define_methods)
end
end
Implements
# Interfaceを実装したクラス
class StudentReport
attr_accessor :title, :author, :body
def initialize(title, author, body)
@title = title
@author = author
@body = make_report_body(body)
end
def make_report_body(body)
# 本当はここで文書を整形する処理が入る
body
end
# Interfaceは定義済みのメソッドを検査するのでincludeは最後
include DocumentInterface
end
class Patent
attr_accessor :title, :author, :body
def initialize(title, author, body)
@title = title
@author = author
@body = make_patent_body(body)
end
def make_patent_body(body)
body
end
include DocumentInterface
end
store
class CopyMachine
attr_accessor :documents
def initialize
@documents = {}
end
def register(document)
id = get_id
@documents.store(id, document)
return id
end
def copy(id)
@documents[id].clone
end
# 採番する処理
def get_id
@documents.length + 1
end
end
Main
class Main
copy_machine = CopyMachine.new
my_report = StudentReport.new("hoge", "fuga", "piyo")
new_patent = Patent.new("pat", "ent", "desu")
report_id = copy_machine.register(my_report)
patent_id = copy_machine.register(new_patent)
copied_report = copy_machine.copy(report_id)
copied_patent = copy_machine.copy(patent_id)
puts copied_report.body
puts copied_patent.body
copied_report.body = "foobar"
pakuri_report_id = copy_machine.register(copied_report)
pakuri_report = copy_machine.copy(pakuri_report_id)
puts pakuri_report.body
end
まとめ
今回は値オブジェクトっぽいもので例えてしまいましたが、
インスタンスが振る舞いを持っているような場合の方が有効なパターンかと思います。
例えば、
- 将棋やオセロの盤面
- 途中から再開して保存するとか
- 履歴もそのインスタンスが保持している
- 年賀状のデータ
- ユーザーが作った画像や文面が保存される
などなど。