LoginSignup
0
2

More than 5 years have passed since last update.

Rubyデザインパターン - 振る舞いに関するパターン

Last updated at Posted at 2017-04-02

Observer

概要

オブジェクトの監視を行う
オブジェクトに変化が発生した時、他のオブジェクトに通知
パラメータを変更した事が他の部分に知れ渡ることが必要なケース

Subject:変化するオブジェクト
Observer:変化を通知する
ConcreteObserver:変化に関連して具体的な処理を行う

メリット

オブジェクト間の依存度を下げる
サブジェクトは通知を意識しなくていい

require 'observer'

class Player
  include Observable

  attr_accessor :times, :level
  attr_reader :name, :item

  def initialize(name, times, level)
    @name = name
    @times = times
    @level = level
    @item = ["Soccer", "Baseball", "Basketball"]
    # 通知するオブジェクトを追加
    add_observer(LevelUp.new)
    add_observer(Play.new)
  end

  def level=(new_level)
    @level = new_level
    # 通知
    changed
    notify_observers(self)
  end
end

# ConcreteObserver 1
class LevelUp
  def update(obj)
    obj.times += 1
    puts "#{obj.times}回目 #{obj.name} : Level => #{obj.level}"
  end
end

# ConcreteObserver 2
class Play
  def update(obj)
    puts "Level #{obj.level} => He can play #{obj.item[obj.times]}"
  end
end

john = Player.new('John', 0, 10)
p "--- Level Up ---"
john.level = 11
p "--- Level Up ---"
john.level = 12

Strategy

概要

処理をオブジェクトとして切り出す(委譲する)
処理の切替を容易にする

Reportから見て、HTMLFormatterとPlaneTextFormatterが同じように使える

# Strategy
class Formatter
  def output_report(title, text)
    raise 'Called abstract method !!'
  end
end
# ConcreteStrategy 1
class HTMLFormatter < Formatter
  @@count = 0
  def output_report(report)
    @@count += 1
    puts "<html><head><title>#{report.title}</title></head><body>"
    puts "<h1>#{report.body}</h1>"
    puts "<h1>#{@@count}回目</h1>"
    report.text.each { |line| puts "<p>#{line}</p>" }
    puts '</body></html>'
  end
end
# ConcreteStrategy 2
class PlaneTextFormatter < Formatter
  @@count = 0
  def output_report(report)
    @@count += 1
    puts "***** #{report.title} *****"
    puts "* #{report.body} *"
    puts "#{@@count}回目"
    report.text.each { |line| puts(line) }
  end
end

# Report.formatterにHTMLかTextかを保持する
# Report.formatter.output_report(self)
class Report
  attr_reader :title, :text, :body
  attr_accessor :formatter
  def initialize(formatter)
    @title = 'Report Title'
    @text = %w(text1)
    @body = 'Hello World!'
    @formatter = formatter
  end
  def output_report
    @formatter.output_report(self)
  end
end

report = Report.new(HTMLFormatter.new)
report.output_report

# formatterを変更するだけ
report.formatter = PlaneTextFormatter.new
report.output_report

# formatterを変更するだけ
report.formatter = HTMLFormatter.new
report.output_report

# <html><head><title>Report Title</title></head><body>
# <h1>Hello World!</h1>
# <h1>1回目</h1>
# <p>text1</p>
# </body></html>
# ***** Report Title *****
# * Hello World! *
# 1回目
# text1
# <html><head><title>Report Title</title></head><body>
# <h1>Hello World!</h1>
# <h1>2回目</h1>
# <p>text1</p>
# </body></html>
# ブロックを利用する
class Report
  attr_reader :title, :text
  attr_accessor :formatter, :count
  def initialize(&formatter)
    @title = 'report title'
    @text = %w(text1 text2)
    @formatter = formatter
    @count = 0
  end
  def output_report
    @count += 1
    @formatter.call(self)
  end
end

HTML_FORMATTER = lambda do |context|
  puts "<html><head><title>#{context.title}</title></head><body>"
  puts "<h1>#{context.count}回目</h1>"
  context.text.each { |line| puts "<p>#{line}</p>" }
  puts '</body></html>'
end

PLANE_TEXT_FORMATTER = lambda do |context|
  puts "***** #{context.title} *****"
  puts "#{context.count}回目"
  context.text.each { |line| puts(line) }
end

report = Report.new(&HTML_FORMATTER)
report.output_report
report.formatter = PLANE_TEXT_FORMATTER
report.output_report

# <html><head><title>report title</title></head><body>
# <h1>1回目</h1>
# <p>text1</p>
# <p>text2</p>
# </body></html>
# ***** report title *****
# 2回目
# text1
# text2

Command

概要

処理をクラスとして切り出し、変わるところと変わらないところを分離する
ベースのCommandクラスは不変部分であり、そこに可変のコマンドオブジェクトを実装する

class Command
  attr_reader :description
  def initialize(description)
    @description = description
  end
  def execute
  end
  def undo_execute
  end
end

require "fileutils"

class CreateFile < Command
  def initialize(path, contents)
    super("Create file : #{path}")
    @path = path
    @contents = contents
  end
  def execute
    f = File.open(@path, "w")
    f.write(@contents)
    f.close
  end
  def undo_execute
    File.delete(@path)
  end
end

class DeleteFile < Command
  def initialize(path)
    super("Delete file : #{path}")
    @path = path
  end
  def execute
    if File.exists?(@path)
      @content = File.read(@path)
    end
    File.delete(@path)
  end
  def undo_execute
    f = File.open(@path, "w")
    f.write(@contents)
    f.close
  end
end

class CopyFile < Command
  def initialize(source, target)
    super("Copy file : #{source} to #{target}")
    @source = source
    @target = target
  end
  def execute
    FileUtils.copy(@source, @target)
  end
  def undo_execute
    File.delete(@target)
    if(@contents)
      f = File.open(@target, "w")
      f.write(@contents)
      f.close
    end
  end
end

class CompositeCommand < Command
  def initialize
    @commands = []
  end
  def add_command(cmd)
    @commands << cmd
  end
  def execute
    @commands.each { |cmd| cmd.execute }
  end
  def undo_execute
    @commands.reverse.each { |cmd| cmd.undo_execute }
  end
  def description
    description = ""
    @commands.each { |cmd| description += cmd.description + "\n"}
    description
  end
end

command_list = CompositeCommand.new
command_list.add_command(CreateFile.new("file1.txt", "hello world\n"))
command_list.add_command(CopyFile.new("file1.txt", "file2.txt"))
command_list.add_command(DeleteFile.new("file1.txt"))
command_list.execute
#<CreateFile:0x007fc9b1d13e18 @contents="hello world\n", @description="Create file : file1.txt", @path="file1.txt">,
#<CopyFile:0x007fc9b50f3800 @description="Copy file : file1.txt to file2.txt", @source="file1.txt", @target="file2.txt">,
#<DeleteFile:0x007fc9b1d4b868 @content="hello world\n", @description="Delete file : file1.txt", @path="file1.txt">

command_list.undo_execute
#<DeleteFile:0x007fc9b1d4b868 @content="hello world\n", @description="Delete file : file1.txt", @path="file1.txt">,
#<CopyFile:0x007fc9b50f3800 @description="Copy file : file1.txt to file2.txt", @source="file1.txt", @target="file2.txt">,
#<CreateFile:0x007fc9b1d13e18 @contents="hello world\n", @description="Create file : file1.txt", @path="file1.txt">

Template Method

概要

抽象的な処理をベースのクラス側に
サブクラスに変化するロジックを

class TemplateMethod
  def initialize
  end
  def hoge
    puts hoge1
    puts hoge2
    puts hoge3
  end
  def hoge1
  end
  def hoge2
  end
  def hoge3
  end
end

class Sub1 < TemplateMethod
  def hoge1
    "--hoge1--"
  end
  def hoge2
    "--hoge2--"
  end
  def hoge3
    "--hoge3--"
  end
end

class Sub2 < TemplateMethod
  def hoge1
    "**hoge1**"
  end
  def hoge2
    "**hoge2**"
  end
  def hoge3
    "**hoge3**"
  end
end

template = TemplateMethod.new()
template.hoge

template1 = Sub1.new()
template1.hoge

template2 = Sub2.new()
template2.hoge

Iterator

概要

集合を数え上げる処理を共通化することで、集合を扱いやすくする

a.rb
class Iterator
  def initialize(array)
    @array = array
    @index = 0
  end
  def has_next
    return (@index + 1) <= @array.count
  end
  def next
    value = @array[@index]
    @index = @index + 1
    value
  end
end
b.rb
require_relative 'a'

iterator = Iterator.new(%w(hoge huga piyo))

while iterator.has_next
  puts(iterator.next)
end
# hoge
# huga
# piyo
0
2
0

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
2