Rubyによるデザインパターンまとめ : iterator

Rubyによるデザインパターン

Rubyによるデザインパターン

利用する場面

あるオブジェクトが複数の子要素をもっており、それを連続して走査したい場合。

(この親要素を集約オブジェクトと呼ぶ。)

方法

  • 外部イテレータ
    • 集約オブジェクトとは別にiterateするためのオブジェクトをつくる。
    • 外部イテレータに#nextみたいなメッセージを送ることで、外部からiterateを操作する。
  • 内部イテレータ
    • 集約オブジェクトと別にiterateするためのオブジェクトをつくらない。
    • ブロックを受け取って、後は内部でガーッとiterateする。その過程でブロックをyieldする。

その他

  • Javaではもっぱら外部イテレータを使うらしい。
  • Rubyには組み込みメソッドで色んな便利なiteratorが存在する。
  • ほとんどは内部イテレータだが、Fileオブジェクトは外部イテレータとして使うことができる。
    • File#readlineで1つすすみ、File#eof?で位置を確認する。

サンプル

外部イテレータ

class Parent
  attr_reader :children

  def initialize(*children)
    @children = children
  end
end

class Child
  def initialize(name)
    @name = name
  end
end

class ChildrenIterator
  def initialize(parent)
    @parent = parent
    @index = 0
  end

  def has_next?
    @index < @parent.children.length - 1
  end

  def child
    @parent.children[@index]
  end

  def next
    raise "no younger child" unless has_next?
    @index += 1
  end
end

namihei = Parent.new(Child.new(:sazae), Child.new(:katsuo), Child.new(:wakame))
i = ChildrenIterator.new(namihei)

while true do
  p i.child
  i.has_next? ? i.next : break
end
# => #<Child:0x007ff53c195aa8 @name=:sazae>, #<Child:0x007fa40307d290 @name=:katsuo>, #<Child:0x007fa40307d268 @name=:wakame>

内部イテレータ①:地に直接iterateするためのメソッドを書く。

class Parent
  attr_reader :children

  def initialize(*children)
    @children = children
  end
end

class Child
  def initialize(name)
    @name = name
  end
end

def iterate_children(parent)
  i = 0
  while i < parent.children.size
    yield(parent.children[i])
    i += 1
  end
end

namihei = Parent.new(Child.new(:sazae), Child.new(:katsuo), Child.new(:wakame))
iterate_children(namihei) { |child| p child }
# => #<Child:0x007f9a971976f0 @name=:sazae>, #<Child:0x007f9a97197650 @name=:katsuo>, #<Child:0x007f9a97197628 @name=:wakame>

内部イテレータ②:集約オブジェクトにiterateするためのメソッドを書く。

class Parent
  attr_reader :children

  def initialize(*children)
    @children = children
  end

  def iterate_children
    i = 0
    while i < children.size
      yield(children[i])
      i += 1
    end
  end
end

class Child
  def initialize(name)
    @name = name
  end
end

namihei = Parent.new(Child.new(:sazae), Child.new(:katsuo), Child.new(:wakame))
namihei.iterate_children { |child| p child }
# => #<Child:0x007f9a971976f0 @name=:sazae>, #<Child:0x007f9a97197650 @name=:katsuo>, #<Child:0x007f9a97197628 @name=:wakame>