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

![](https://ruby.tw/logo.gif)

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

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

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

### 例

```ruby
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が同じように使える

```ruby
# 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>
```

```ruby
# ブロックを利用する
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クラスは不変部分であり、そこに可変のコマンドオブジェクトを実装する

### 例

```rb
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
### 概要
抽象的な処理をベースのクラス側に
サブクラスに変化するロジックを

### 例

```rb
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
### 概要
集合を数え上げる処理を共通化することで、集合を扱いやすくする

### 例

```rb: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
```

```rb:b.rb
require_relative 'a'

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

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