GoFのデザインパターンのイーテレータ(Iterator)のRubyコードを使った紹介記事です。イーテレーターパターンは次のような場合に使います。
- 要素の集まったオブジェクト(配列など)にアクセスする
- 集合の要素に順にアクセスする必要がある
😎イーテレータとは?
GoFではイーテレータを「集約オブジェクトがもとにある内部表現を公開せずに、その要素に順にアクセスする方法を提供する」と定義しています。
言い換えると、要素の集まりをもつオブジェクトの各要素に、順番にアクセスする方法を提供するためのデザインパターンです。
🚕内部イーテレータ
Rubyのeachと同じもの。コードブロックベースのイーテレータのこと。
🐯外部イーテレータ
ここでは次のようなモデルで外部イーテレータを説明します。
Blogクラス(集約オブジェクト): 複数のArticleクラスをもつオブジェクトArticleクラス(集約オブジェクト内の要素):Blogクラスの個別の要素BlogIteratorクラス(外部イーテレータ):Blogの要素Articleにアクセスするためクラス
まず、記事を表すArticleクラスです。
# 記事を表す(集約オブジェクト中の要素) classArticle definitialize(title) @title = title end
# 記事のタイトル attr_reader:title end
|
次は、記事を複数もつブログを表すBlogクラスです。
# ブログを表す(集約オブジェクト) classBlog definitialize @articles = [] end
# 指定インデックスの要素を返す defget_article_at(index) @articles[index] end
# 要素(Article)を追加する defadd_article(article) @articles<< article end
# 要素(Article)の数を返す deflength @articles.length end
# イーテレータの生成 defiterator BlogIterator.new(self) end end
|
最後に外部イーテレータのBlogIteratorクラスです。
# 外部イーテレータ classBlogIterator definitialize(blog) @blog = blog @index =0 end
# 次のindexの要素が存在するかをtrue/falseで返す defhas_next? @index< @blog.length end
# indexを1つ進めて、次のArticleクラスを返す defnext_article article =self.has_next? ? @blog.get_article_at(@index) :nil @index = @index +1 article end end
|
ソースコードは以上です。では、実際の動作を確認してみます。
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?」といった便利なメソッドを取り込むことができます。
以下は取り込んだ場合のサンプルソースです。
classAccount attr_accessor:name,:balance
definitialize(name, balance) @name = name @balance = balance end
def<=>=>(other) @balance<=> other.balance=> end end
classPortfolio include Enumerable
definitialize @accounts = [] end
defeach(&block) @accounts.each(&block) end
defadd_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
|
😸サンプルソース
🤔参考リンク
🖥 VULTRおすすめ
「VULTR」はVPSサーバのサービスです。日本にリージョンがあり、最安は512MBで2.5ドル/月($0.004/時間)で借りることができます。4GBメモリでも月20ドルです。 最近はVULTRのヘビーユーザーになので、「ここ」から会員登録してもらえるとサービス開発が捗ります!