Rubyによるデザインパターン【Iterator】-君の子供たちに伝えたいのだけど-


# 概要

[Rubyによるデザインパターン](http://www.amazon.co.jp/gp/product/4894712857/ref=as_li_qf_sp_asin_tl?ie=UTF8&camp=247&creative=1211&creativeASIN=4894712857&linkCode=as2&tag=kidachi-22)第7章。
Iterator Pattern。

[Rubyによるデザインパターン5原則](http://qiita.com/kidachi_/items/4b63de9ad5a97726c50c)に則って理解する。


# どんなパターンか

集約オブジェクト（コンポジット）の全ての子オブジェクトを連続で走査し、また任意のロジックを適用する。

イテレータ用の外部クラスを用いて実現する外部イテレータと、
（例えばeachのように）オブジェクト自身がブロックを受け取り、子オブジェクトに適用する内部イテレータが存在する。

# 内部イテレータ



コードブロックの利用によって集約オブジェクトにロジックを伝える
（子オブジェクトたちそれぞれに対してコードブロックを呼ぶ）。
繰り返し処理の全てが集約オブジェクト内で起こるため、内部イテレータと呼ぶ。


## 配列を内部イテレータで走査

Arrayにはもともとイテレータメソッドeachが存在する。

## 

```ruby
array = ['red', 'green', 'blue']

array.each do |val|
  p val
end
"red"
"green"
"blue"
```

手軽。

# 外部イテレータ

イテレータ用の外部クラスArrayIteratorを作成する。

```ruby
class ArrayIterator
  def initialize(array)
    @array = array
    @index = 0
  end

  def has_next?
    @index < @array.length
  end

  def item
    @array[@index]
  end

  def next_item
    value = @array[@index]
    @index += 1
    value
  end
end
```


## 配列を外部イテレータで走査

```ruby
array = ['red', 'green', 'blue']
i = ArrayIterator.new(array)

while i.has_next?
  puts("item: #{i.next_item}")
end
=>item: red
item: green
item: blue
```

## 文字列を外部イテレータで走査

- lengthメソッドを持っていること
- 整数でインデックスを付与できること

これらを満たした集約クラスなら、どんなものに対しても機能する。
→つまり、文字列にも適応可。


```ruby
i = ArrayIterator.new('abc')

while i.has_next?
  puts("item: #{i.next_item}")
end
=>item: a
item: b
item: c
```


# 内部イテレータ vs 外部イテレータ



### 内部イテレータ

外部イテレータでは、余分にイテレータオブジェクト（例ではArrayIterator）が必要であるのに対して
シンプル、分かりやすい。

### 外部イテレータ

外部機能として渡すため、複数コレクションから一つずつ要素を取り出して並行に処理するようなことも可能。

例として、配列を結合するmerge機能を追加してみる。

```ruby
array1 = [10, 50, 100]
array2 = [20, 30, 150]

def merge(array1, array2)
  merged = []

  iterator1 = ArrayIterator.new(array1)
  iterator2 = ArrayIterator.new(array2)

  while(iterator1.has_next? and iterator2.has_next?)
    if iterator1.item < iterator2.item
      merged << iterator1.next_item
    else
      merged << iterator2.next_item
    end
  end

  # array1から残りを取り出す
  while(iterator1.has_next?)
    merged << iterator1.next_item
  end

  # array2から残りを取り出す
  while(iterator2.has_next?)
    merged << iterator2.next_item
  end

  merged
end

merge(array1, array2)
=> [10, 20, 30, 50, 100, 150]
```

内部イテレータだとこのような並行処理は難しい。


# Enumerable

集約クラスに対して内部イテレータを持たせた場合、そもそもEnumerableモジュールのMix-inを検討すべき。

module Enumerable
http://docs.ruby-lang.org/ja/2.0.0/class/Enumerable.html


複数のAccountを保持する集約クラスPortfolioにEnumerableをMix-inする例。

```ruby
class Account
  attr_accessor :name, :balance

  def initialize(name, balance)
    @name = name
    @balance = balance
  end
end

class Portfolio
  include Enumerable

  def initialize
    @accounts = []
  end

  # Enumerable moduleをincludeしたクラスはeachメソッドを持っている必要がある
  def each(&block)  
    @accounts.each(&block)
  end

  def add_account(account)
    @accounts << account
  end
end


my_portfolio = Portfolio.new
my_portfolio.add_account(Account.new('test', 1000))
my_portfolio.any? {|account| account.balance > 100 }
=> true
```


# Iteratorパターン使用上の注意


集約オブジェクトを繰り返し走査しているタイミングで該当オブジェクトが変更されたらどうなるか？

→ ここまでのイテレータでは、不具合が発生する可能性がある。

## 内部イテレータ

走査済みの値が削除されると、インデックスがずれてしまう。

```ruby
array = ['red', 'green', 'blue']

array.each do |val|
  p val
  if val=='red'
    array.delete(val)
  end
end
=>"red"  # インデックスがずれてgreenが無視されてしまう
"blue"
```

### 対策

delete_ifを使って解決。

```ruby
array = ['red', 'green', 'blue']

array.delete_if do |val|
  p val
  val=='red'
end
=>"green"  # redを正常に削除
"blue"
```

deleteした後もインデックスを正しく保ってくれる。


## 外部イテレータ

以下のように集約のひとつひとつを走査するごとにそれに合わせてインデックスを追加しているため、

```ruby
  def next_item
    value = @array[@index]
    @index += 1
    value
  end
```

もし走査済みの値が削除されるとインデックスがずれてしまう。

### 対策

イテレータのコンストラクタで配列のコピーを作成、値の削除に影響を受けないようにする。

```ruby
class ChangeResistantArrayIterator
  def initialize(array)
    @array = Array.new(array)
    @index = 0
  end
end
```

※ この手法は単純に「イテレータで走査し始めたタイミングのコレクションに対するアウトプットを約束する」ものであり、
「走査中にコレクションに変更が加わった場合にも適応してくれる」delete_ifとは異なる。


# Iteratorの実例

[Ruby] 便利な組み込みクラスのメソッド達（Enumerable編）
http://qiita.com/kidachi_/items/a00558cfb0a6a3e23f4b
[Ruby] 便利な組み込みクラスのメソッド達（Hash編）
http://qiita.com/kidachi_/items/651b5b5580be40ad047e


## 変態系

### ObjectSpace * each_object

RubyインタプリタにロードされているすべてのRubyオブジェクトへアクセスできる。

```ruby
# 実行注意
> ObjectSpace.each_object {|object| puts("Object: #{object}")}
Object: SyntaxError
Object: SyntaxError
Object: ScriptError
Object: ScriptError
Object: RangeError
Object: RangeError
Object: KeyError
Object: KeyError
Object: IndexError
Object: IndexError
Object: ArgumentError
Object: ArgumentError
Object: TypeError
Object: TypeError
Object: StandardError
Object: StandardError
Object: Interrupt
Object: Interrupt
Object: SignalException
Object: SignalException
Object: fatal
Object: fatal
Object: success?
Object: status
Object: SystemExit
・・・
```


Railsではこれを利用して、ObjectSpaceを使用して与えられたクラスの
サブクラスをすべて探し出すメソッドを用意している。

```ruby
def subclasses_of(superclass)
  subclasses = []
  ObjectSpace.each_object(Class) do |k|
    next if !k.ancestors.include?(superclass) ||
        superclass ==k ||
        k.to_s.include?('::') ||
        subclasses.include?(k.to_s)
    subclasses << k.to_s
  end
  subclasses
end

subclasses_of(Numeric)
=> ["Complex", "Rational", "Bignum", "Float", "Fixnum", "Integer"]
```

全オブジェクトを走査、該当するものだけ抜き出すとはなかなか。

# まとめ

- 集約コレクションの子オブジェクトにアクセスすることが可能。
- 並行処理の必要がなければ基本は内部イテレータが手軽（Rubyの場合）。

# 以下に続く（予定）

【Command】パターン
WIP.