LoginSignup
0

More than 3 years have passed since last update.

prototypeパターン

Posted at

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

スクリーンショット 2019-04-07 12.26.04.png

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

まとめ

今回は値オブジェクトっぽいもので例えてしまいましたが、
インスタンスが振る舞いを持っているような場合の方が有効なパターンかと思います。

例えば、

  • 将棋やオセロの盤面
    • 途中から再開して保存するとか
    • 履歴もそのインスタンスが保持している
  • 年賀状のデータ
    • ユーザーが作った画像や文面が保存される

などなど。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0