酒と泪とRubyとRailsと

Ruby on Rails と Objective-C は酒の肴です!

イーテレータ Ruby 2.0.0 デザインパターン速攻習得[Iterator][Design Pattern]

GoFのデザインパターン(Design Pattern)のイーテレータ(Iterator )のRubyコードを使った紹介記事です。

イーテレーターパターンは次のような場合に使います。

* 要素の集まったオブジェクト(配列など)にアクセスする
* 集合の要素に順にアクセスする必要がある

イーテレータとは?

GoFではイーテレータを次のように定義しています。

集約オブジェクトがもとにある内部表現を公開せずに、その要素に順にアクセスする方法を提供する

言い換えると、要素の集まりを持つオブジェクトの各要素に、順番にアクセスする方法を提供するためのデザインパターンです。

内部イーテレータ

Rubyのeachと同じもの。コードブロックベースのイーテレータのこと。

外部イーテレータ

ここでは次のようなモデルで外部イーテレータを説明します。

Blogクラス(集約オブジェクト): 複数のArticleクラスを持つオブジェクト
Articleクラス(集約オブジェクト内の要素): Blogクラスの個別の要素
BlogIteratorクラス(外部イーテレータ): Blogの要素Articleにアクセスするためクラス

まず、記事を表すArticleクラスです。

1
2
3
4
5
6
7
8
9
# 記事を表す(集約オブジェクト中の要素)
class Article
  def initialize(title)
    @title = title
  end

  # 記事のタイトル
  attr_reader :title
end

次は、記事を複数持つブログを表すBlogクラスです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# ブログを表す(集約オブジェクト)
class Blog
  def initialize
    @articles = []
  end

  # 指定インデックスの要素を返す
  def get_article_at(index)
    @articles[index]
  end

  # 要素(Article)を追加する
  def add_article(article)
    @articles << article
  end

  # 要素(Article)の数を返す
  def length
    @articles.length
  end

  # イーテレータの生成
  def iterator
    BlogIterator.new(self)
  end
end

最後に外部イーテレータであるBlogIteratorクラスです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 外部イーテレータ
class BlogIterator
  def initialize(blog)
    @blog = blog
    @index = 0
  end

  # 次のindexの要素が存在するかをtrue/falseで返す
  def has_next?
    @index < @blog.length
  end

  # indexを1つ進めて、次のArticleクラスを返す
  def next_article
    article = self.has_next? ? @blog.get_article_at(@index) : nil
    @index = @index + 1
    article
  end
end

ソースコードは以上です。では、実際の動作を確認してみます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
blog = Blog.new
blog.add_article(Article.new("デザインパターン1"))
blog.add_article(Article.new("デザインパターン2"))
blog.add_article(Article.new("デザインパターン3"))
blog.add_article(Article.new("デザインパターン4"))

iterator = blog.iterator
while iterator.has_next?
  article = iterator.next_article
  puts article.title
end
#デザインパターン1
#デザインパターン2
#デザインパターン3
#デザインパターン4

blog.iteratorで生成した外部イーテレータによって、Blogクラスの要素Articleに順番にアクセスできている事がわかります。

Enumerable モジュール

唐突ですが、Rubyの便利モジュール Enumerableの紹介です。Enumerable モジュールをインクルードすると、集約オブジェクト向けの「all?やany?、include?」といった便利なメソッドを取り込むことができます。

以下は取り込んだ場合のサンプルソースです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# -*- coding: utf-8 -*-

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

portfolio = Portfolio.new
portfolio.add_account(Account.new("account1", 1000))
portfolio.add_account(Account.new("account2", 2000))
portfolio.add_account(Account.new("account3", 3000))
portfolio.add_account(Account.new("account4", 4000))
portfolio.add_account(Account.new("account5", 5000))

# $3000より多く所有している口座があるか?
puts portfolio.any? { |account| account.balance > 3000 }
#=> true

# すべての口座が$2000以上あるか?
puts portfolio.all? { |account| account.balance >= 2000 }
#=> false

このサンプルソースはGitHubにも置いています。

サンプルソース(GitHub)

Special Thanks

Iterator - Murayama blog.

1. Iteratorパターン 1 | TECHSCORE(テックスコア)

[designpattern] rubyで学ぼうデザインパターン ~1,Iteratorパターン~

Amazon.co.jp: Rubyによるデザインパターン: Russ Olsen, ラス・オルセン, 小林 健一, 菅野 裕, 吉野 雅人, 山岸 夢人, 小島 努: 本

変更来歴

12/10 09:00 GitHubへのサンプルソースの設置。導入文の修正
12/11 00:00 書籍へのリンクをAmazon アフィリエイトに変更
12/11 10:50 全体に説明を追加
06/20 18:00 Ruby2.0.0対応、読みづらい部分を修正

おすすめの書籍