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)