Design Patterns: Ruby Companion
Provide a way to access the elements of a aggregate object without exposing its underlying representation.
# Iterator Pattern # sample1 class Book def initialize(name) @name = name end attr_reader :name end class BookShelf def initialize() @books = Array.new() end def [](index) return @books[index] end def append_book(book) @books << book end def length return @books.length end def iterator return BookShelfIterator.new(self) end end class BookShelfIterator def initialize(bookshelf) @bookShelf = bookshelf @index = 0 end def has_next?() @index < @bookShelf.length end def next() book = @bookShelf[@index] @index += 1 return book end end ### main if __FILE__ == $0 bookShelf = BookShelf.new() bookShelf.append_book(Book.new("Around the World in 80 Days")) bookShelf.append_book(Book.new("Bible")) bookShelf.append_book(Book.new("Cinderella")) bookShelf.append_book(Book.new("Daddy-Long-Legs")) it = bookShelf.iterator() while it.has_next? book = it.next() print book.name, "\n" end end
In sample 1, there is a two objects: a container and a iterator which scan container. When you iterate, you need to call next method.
But in Ruby, we usually use iterator without next method. Ruby's iterator is called 'internal iterator'. It is a method that the container has. Iterator like sample 1 is called 'external iterator'.
A sample of internal iterator is bellow. each method is used as iterator method.
# Iterator Pattern # sample2: using Enumerable class Book def initialize(name) @name = name end attr_reader :name end class BookShelf include Enumerable def initialize() @books = Array.new() end def [](index) @books[index] end def <<(book) @books << book end def length() @books.length end def each() @books.each{|book| yield(book) } end end ## main if __FILE__ == $0 bookShelf = BookShelf.new() bookShelf << Book.new("Around the World in 80 Days") bookShelf << Book.new("Bible") bookShelf << Book.new("Cinderella") bookShelf << Book.new("Daddy-Long-Legs") bookShelf.each{|book| print book.name, "\n" } bookstr = bookShelf.collect{|book| '"'+book.name+'"' }.join(":") print bookstr, "\n" end
The definition of BookShelf class in sample 2 tell me BookShelf class has almost same functions with Array class. So, you can use Array class instead of BookShelf. But when you use it you cannot extend container class without influence to other class using Array. So let's use Inherited class of Array.
# Iterator Pattern # sample3: using Struct and inheretance of Array class ## Book class Book = Struct.new("Book", :name) ## BookShelf class class BookShelf < Array; end ## main if __FILE__ == $0 bookShelf = BookShelf.new() bookShelf << Book.new("Around the World in 80 Days") bookShelf << Book.new("Bible") bookShelf << Book.new("Cinderella") bookShelf << Book.new("Daddy-Long-Legs") bookShelf.each{|book| print book.name, "\n" } bookstr = bookShelf.collect{|book| '"'+book.name+'"' }.join(":") print bookstr, "\n" end
Struct class makes classes with attributes. This classes have attributes and access methods(setter and getter) to the attributes. Notice that instance of Struct is a new class itself.
In this sample, we use delegation instead of inheritance. With deletation, we can use other class' functions without inheritance.
# Iterator Pattern # sample4: using delegate require 'delegate' ## Book class Book = Struct.new("Book", :name) ## BookShelf class class BookShelf < DelegateClass(Array) def initialize() super([]) end end ## main if __FILE__ == $0 bookShelf = BookShelf.new() bookShelf << Book.new("Around the World in 80 Days") bookShelf << Book.new("Bible") bookShelf << Book.new("Cinderella") bookShelf << Book.new("Daddy-Long-Legs") bookShelf.each{|book| print book.name, "\n" } bookstr = bookShelf.collect{|book| '"'+book.name+'"' }.join(":") print bookstr, "\n" end
Deletagor class delegages (almost) all methods of original class.
When you delegetes a few methods, you can use forwardable library. Forwardable class delegates only specified methods.
# Iterator Pattern # sample5: using forwarding require 'forwardable' ## Book class Book = Struct.new("Book", :name) ## BookShelf class class BookShelf extend Forwardable def_delegators("@books", "<<", "[]", "length", "each") include Enumerable def initialize() @books = Array.new() end end ## main if __FILE__ == $0 bookShelf = BookShelf.new() bookShelf << Book.new("Around the World in 80 Days") bookShelf << Book.new("Bible") bookShelf << Book.new("Cinderella") bookShelf << Book.new("Daddy-Long-Legs") bookShelf.each{|book| print book.name, "\n" } bookstr = bookShelf.collect{|book| '"'+book.name+'"' }.join(":") print bookstr, "\n" end
Original version of this sources are written by YUKI Hiroshi-san in "Introduction to Design Patterns with Java", and modified totally by TAKAHASHI Masayoshi to use Ruby.
License of Origanal sources is <URL:http://www.hyuki.com/dp/index.html#download> (in Japanese and English). This modified version is under the same license.
Author: TAKAHASHI 'Maki' Masayoshi (maki@rubycolor.org)