
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対応、読みづらい部分を修正