Iterator
Rubyによるデザインパターン生活、何日目とかもういいや。
本日はIteratorパターンを取り上げます。
- 作者: Russ Olsen,ラス・オルセン,小林健一,菅野裕,吉野雅人,山岸夢人,小島努
- 出版社/メーカー: ピアソン桐原
- 発売日: 2009/04/01
- メディア: 単行本
- 購入: 12人 クリック: 193回
- この商品を含むブログ (58件) を見る
Iteratorパターンは、
「集約オブジェクトがもとにある内部表現を公開せずに、その要素に順にアクセスする方法を提供する」とGoFは定義しています。
Javaでよく出てくるIteratorインタフェースがまさにそれです。
Iteratorインタフェースを利用することでListオブジェクトなど内部を意識せず、順番に要素へアクセスすることができます。
RubyにおけるIteratorパターンでは、外部イテレータと内部イテレータが存在します。
外部イテレータってのは、JavaのIteratorインタフェースのようなクラスを自分で作ってしまう方法で、
内部イテレータってのは、Rubyでよく見るeach{|element| #do something}のようなメソッドを意味します。
*1
で、まずは外部イテレータを試してみます。
配列オブジェクト用のイテレータ(ArrayIterator)クラス。
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
使い方はこんなかんじ。
array = ["red", "green", "blue"] it = ArrayIterator.new(array) while it.has_next? puts "item: #{it.next_item}" end
そんで実行結果。
item: red item: green item: blue
ちなみに名前はArrayIteratorだけど、配列オブジェクトにしか使えないわけではなくて、文字列オブジェクトの操作にも使えます。
it = ArrayIterator.new("test") while it.has_next? puts "item:#{it.next_item.chr}" end
文字列操作の場合は文字コードが返されるので、chrメソッドで文字データに変換しています。
実行結果は以下のとおり。
item:t item:e item:s item:t
続いて、内部イテレータの話です。
Arrayオブジェクトに定義されているeachメソッドのようなものを独自で実装すると以下のようになります。
def for_each_element(array) i = 0 while i < array.length yield(array[i]) i += 1 end end
引数で受け取ったArrayオブジェクトに対して、yieldメソッドでコードブロックを順に実行するかんじです。
呼び出し側のプログラムは以下のようになります。
a = [10, 20, 30] for_each_element(a){ |element| puts element }
実行結果です。
10 20 30
外部イテレータより、内部イテレータの方がRubyっぽいというか便利なように思います。
ただし、2つのArrayオブジェクトをマージするような場合は、
外部イテレータを使う方が楽なようです。
def merge(array1, array2) merged = [] it1 = ArrayIterator.new(array1) it2 = ArrayIterator.new(array2) while it1.has_next? && it2.has_next? if it1.item < it2.item merged << it1.next_item else merged << it2.next_item end end while it1.has_next? merged << it1.next_item end while it2.has_next? merged << it2.next_item end merged end a = [10, 20, 30] b = [11, 12, 21, 22] merge(a, b).each { |e| puts e }
マージの実行結果です。
10 11 12 20 21 22 30
Iteratorパターンのお話はこんなところなんだけど、
この章では、Enumerableモジュールの使い方を紹介しています。
Enumerableモジュールをインクルードすると、集約オブジェクトに対して便利なメソッドを取り込むことができます。
たとえば、all?とかany?とかinclude?とか。
Enumerableモジュールをインクルードするには、以下の点を考慮します。
class Account attr_accessor :name, :balance def initialize(name, balance) @name = name @balance = balance end def <=>(other) @balance <=> other.balance end end class Portfolio include Enumerable def initialize @accounts = [] end def each(&block) @accounts.each(&block) end def add_account(account) @accounts << account end end account1 = Account.new("account1", 1000) account2 = Account.new("account2", 5000) account3 = Account.new("account3", 3000) account4 = Account.new("account4", 4000) portfolio = Portfolio.new portfolio.add_account(account1) portfolio.add_account(account2) portfolio.add_account(account3) portfolio.add_account(account4) puts portfolio.any? { |account| account.balance > 2000 } puts portfolio.all? { |account| account.balance > 2000 }
実行すると、trueが表示されます。
true false
あと、この章の最後のところで、ObjectSpaceモジュールも紹介されています。
ObjectSpaceは、Rubyインタープリタの中に存在する完全なオブジェクト空間へのアクセスを提供します。
初めて知ったけど、これけっこう面白い。どこで使えばよいのかはわからないけど。
さっきのプログラムのつづきに以下のコードを追加すると、
ObjectSpace.each_object(Account){ |object| puts object.name }
こんなふうにメモリ上に存在するインスタンスを取得することができます。
account4 account3 account2 account1
以上。
*1:こういう話は言われてみたら、そうだなーって思うけど、言われなかったら意識しないままだと思う。