Design Patterns: Ruby Companion
Provide an interface for creating families of related or dependent objects without specifying their concrete classes.
There are 4 files in Ruby.
factory.rb
listfactory.rb
tablefactory.rb
main.rb
files "foofactory.rb" are not only factory class. they include other parts (List, Tray and Page) creater classes.
factory.rb
class Factory
def Factory.getFactory(klass)
begin
factory = Object.const_get(klass).new
return factory
rescue NameError
print "undefined class: #{klass}.\n"
end
end
end
## abstract
class Item
def initialize(caption)
@caption = caption
end
end
## abstract
class Link < Item
def initialize(caption, url)
super(caption)
@url = url
end
end
## abstract
class Tray < Item
def initialize(caption)
super(caption)
@tray = Array.new()
end
def add(item)
@tray << item
end
end
## abstract
class Page
def initialize(title, author)
@title, @author = title, author
@content = Array.new()
end
def add(item)
@content << item
end
def output
begin
filename = @title + ".html";
File.open(filename, "w"){|f|
f.write(makeHTML())
}
print "#{filename} was created.\n"
rescue
print $!+"\n"
print $@.join("\n")+"\n"
end
end
def makeHTML
raise NotImplementedError
end
end
listfactory.rb
class ListFactory < Factory
def createLink(caption, url)
ListLink.new(caption, url)
end
def createTray(caption)
ListTray.new(caption)
end
def createPage(title, author)
ListPage.new(title, author)
end
end
class ListLink < Link
def makeHTML()
return " <li><a href=\"#{@url}\">#{@caption}</a></li>\n";
end
end
class ListTray < Tray
def makeHTML
items = @tray.collect{|item|
item.makeHTML
}.join('')
buffer = <<EOB
<li>
#{@caption}
<ul>
#{items}
</ul>
</il>
EOB
buffer
end
end
class ListPage < Page
def makeHTML()
items = @content.collect{|item|
item.makeHTML()
}.join('')
buffer = <<EOB
<html><head><title>#{@title}</title></head>
<body>
<h1>#{@title}</h1>
<ul>
#{items}
</ul>
<hr><address>#{@author}</address>
</body></html>
EOB
buffer
end
end
tablefactory.rb
class TableFactory < Factory
def createLink(caption, url)
TableLink.new(caption, url)
end
def createTray(caption)
TableTray.new(caption)
end
def createPage(title, author)
TablePage.new(title, author)
end
end
class TableLink < Link
def makeHTML
"<td><a href=\"#{@url}\">#{@caption}</a></td>\n"
end
end
class TableTray < Tray
def makeHTML
items = @tray.collect{|item| item.makeHTML()}.join('')
buffer = <<"EOB"
<td>
<table width="100%" border="1"><tr>
<td bgcolor="#cccccc" align="center" colspan="#{@tray.size()}">
<b>#{@caption}</b>
</td>
</tr>
<tr>
#{items}
</tr>
</table>
</td>
EOB
buffer
end
end
class TablePage < Page
def makeHTML
items = @content.collect{|item| "<tr>#{item.makeHTML()}</tr>"}.join('')
buffer = <<EOB
<html><head><title>#{@title}</title></head>
<body>
<h1>#{@title}</h1>
<table with="80%" border="3">
#{items}
</table>
<hr>
<address>#{@author}</address>
</body>
</html>
EOB
buffer
end
end
main.rb
require 'factory.rb'
require 'listfactory.rb'
require 'tablefactory.rb'
def usage()
print "Usage: ruby main.rb <class name of ConcreteFactory>\n"
print "Example 1: ruby main.rb ListFactory\n"
print "Example 2: ruby main.rb TableFactory\n"
end
## main
if ARGV.length != 1
usage()
exit(0)
end
factory = Factory.getFactory(ARGV[0])
asahi = factory.createLink("ASAHI newspaper", "http://www.asahi.com/")
yomiuri = factory.createLink("YOMIYURI newspaper", "http://www.yomiuri.co.jp/")
us_yahoo = factory.createLink("Yahoo!", "http://www.yahoo.com/")
jp_yahoo = factory.createLink("Yahoo!Japan", "http://www.yahoo.co.jp/")
excite = factory.createLink("Excite", "http://www.excite.com/")
google = factory.createLink("Google", "http://www.google.com/")
traynews = factory.createTray("Newspaper")
traynews.add(asahi)
traynews.add(yomiuri)
trayyahoo = factory.createTray("Yahoo!")
trayyahoo.add(us_yahoo)
trayyahoo.add(jp_yahoo)
traysearch = factory.createTray("Search Engine")
traysearch.add(trayyahoo)
traysearch.add(excite)
traysearch.add(google)
page = factory.createPage("LinkPage", "YUKI, Hiroshi")
page.add(traynews)
page.add(traysearch)
page.output()
This sample uses Constant Method Pattern (Idiom). Constant Method Pattern is a kind of Template Method Pattern. It return the class itself, not instance of the class.
factory.rb
## Constant Method Solution
## DPSC p.38
class Factory
def Factory.getFactory(klass)
begin
factory = Object.const_get(klass).new
return factory
rescue NameError
print "undefined class: #{klass}\n"
rescue
raise
end
end
def createLink(*args)
linkClass.new(*args)
end
def createTray(*args)
trayClass.new(*args)
end
def createPage(*args)
pageClass.new(*args)
end
end
## abstract
class Item
def initialize(caption)
@caption = caption
end
end
## abstract
class Link < Item
def initialize(caption, url)
super(caption)
@url = url
end
end
## abstract
class Tray < Item
def initialize(caption)
super(caption)
@tray = Array.new()
end
def add(item)
@tray << item
end
end
## abstract
class Page
def initialize(title, author)
@title, @author = title, author
@content = Array.new()
end
def add(item)
@content << item
end
def output
begin
filename = @title + ".html";
File.open(filename, "w"){|f|
f.write(makeHTML())
}
print "#{filename} was created.\n"
rescue
print $!+"\n"
print $@.join("\n")+"\n"
end
end
def makeHTML
raise NotImplementedError
end
end
listfactory.rb
class ListFactory < Factory
def linkClass()
ListLink
end
def trayClass()
ListTray
end
def pageClass()
ListPage
end
end
class ListLink < Link
def makeHTML()
return " <li><a href=\"#{@url}\">#{@caption}</a></li>\n";
end
end
class ListTray < Tray
def makeHTML
items = @tray.collect{|item|
item.makeHTML
}.join('')
buffer = <<EOB
<li>
#{@caption}
<ul>
#{items}
</ul>
</il>
EOB
buffer
end
end
class ListPage < Page
def makeHTML()
items = @content.collect{|item|
item.makeHTML()
}.join('')
buffer = <<EOB
<html><head><title>#{@title}</title></head>
<body>
<h1>#{@title}</h1>
<ul>
#{items}
</ul>
<hr><address>#{@author}</address>
</body></html>
EOB
buffer
end
end
tablefactory.rb
class TableFactory < Factory
def linkClass()
TableLink
end
def trayClass()
TableTray
end
def pageClass()
TablePage
end
end
class TableLink < Link
def makeHTML
"<td><a href=\"#{@url}\">#{@caption}</a></td>\n"
end
end
class TableTray < Tray
def makeHTML
items = @tray.collect{|item| item.makeHTML()}.join('')
buffer = <<"EOB"
<td>
<table width="100%" border="1"><tr>
<td bgcolor="#cccccc" align="center" colspan="#{@tray.size()}">
<b>#{@caption}</b>
</td>
</tr>
<tr>
#{items}
</tr>
</table>
</td>
EOB
buffer
end
end
class TablePage < Page
def makeHTML
items = @content.collect{|item| "<tr>#{item.makeHTML()}</tr>"}.join('')
buffer = <<EOB
<html><head><title>#{@title}</title></head>
<body>
<h1>#{@title}</h1>
<table with="80%" border="3">
#{items}
</table>
<hr>
<address>#{@author}</address>
</body>
</html>
EOB
buffer
end
end
main.rb
require 'factory.rb'
require 'listfactory.rb'
require 'tablefactory.rb'
def usage()
print "Usage: ruby main.rb <class name of ConcreteFactory>\n"
print "Example 1: ruby main.rb ListFactory\n"
print "Example 2: ruby main.rb TableFactory\n"
end
## main
if ARGV.length != 1
usage()
exit(0)
end
factory = Factory.getFactory(ARGV[0])
asahi = factory.createLink("ASAHI newspaper", "http://www.asahi.com/")
yomiuri = factory.createLink("YOMIYURI newspaper", "http://www.yomiuri.co.jp/")
us_yahoo = factory.createLink("Yahoo!", "http://www.yahoo.com/")
jp_yahoo = factory.createLink("Yahoo!Japan", "http://www.yahoo.co.jp/")
excite = factory.createLink("Excite", "http://www.excite.com/")
google = factory.createLink("Google", "http://www.google.com/")
traynews = factory.createTray("Newspaper")
traynews.add(asahi)
traynews.add(yomiuri)
trayyahoo = factory.createTray("Yahoo!")
trayyahoo.add(us_yahoo)
trayyahoo.add(jp_yahoo)
traysearch = factory.createTray("Search Engine")
traysearch.add(trayyahoo)
traysearch.add(excite)
traysearch.add(google)
page = factory.createPage("LinkPage", "YUKI, Hiroshi")
page.add(traynews)
page.add(traysearch)
page.output()
This sample uses Parts Catalog Idiom. Parts Cagalog is Hash object, which is belong in the factory object. The hash has symbols as its key, and related Class objects as its value. When you create objects, you can send a method new to hash's value.
No method is defined in subclasses of Factory class; super classes's methods are used in them.
factory.rb
## partsCatalog
## DPSC p.xx
class Factory
def Factory.getFactory(klass)
begin
factory = Object.const_get(klass).new
return factory
rescue NameError
print "undefined class: #{klass}\n"
end
end
def initialize
@partsCatalog = nil
end
def create(part, *args)
@partsCatalog[part].new(*args)
end
end
## abstract
class Item
def initialize(caption)
@caption = caption
end
end
## abstract
class Link < Item
def initialize(caption, url)
super(caption)
@url = url
end
end
## abstract
class Tray < Item
def initialize(caption)
super(caption)
@tray = Array.new()
end
def add(item)
@tray << item
end
end
## abstract
class Page
def initialize(title, author)
@title, @author = title, author
@content = Array.new()
end
def add(item)
@content << item
end
def output
begin
filename = @title + ".html";
File.open(filename, "w"){|f|
f.write(makeHTML())
}
print "#{filename} was created.\n"
rescue
print $!+"\n"
print $@.join("\n")+"\n"
end
end
def makeHTML
raise NotImplementedError
end
end
listfactory.rb
class ListFactory < Factory
def initialize
@partsCatalog = {
:Link => ListLink,
:Tray => ListTray,
:Page => ListPage,
}
end
end
class ListLink < Link
def makeHTML()
return " <li><a href=\"#{@url}\">#{@caption}</a></li>\n";
end
end
class ListTray < Tray
def makeHTML
items = @tray.collect{|item|
item.makeHTML
}.join('')
buffer = <<EOB
<li>
#{@caption}
<ul>
#{items}
</ul>
</il>
EOB
buffer
end
end
class ListPage < Page
def makeHTML()
items = @content.collect{|item|
item.makeHTML()
}.join('')
buffer = <<EOB
<html><head><title>#{@title}</title></head>
<body>
<h1>#{@title}</h1>
<ul>
#{items}
</ul>
<hr><address>#{@author}</address>
</body></html>
EOB
buffer
end
end
tablefactory.rb
class TableFactory < Factory
def initialize
@partsCatalog = {
:Link => TableLink,
:Tray => TableTray,
:Page => TablePage,
}
end
end
class TableLink < Link
def makeHTML
"<td><a href=\"#{@url}\">#{@caption}</a></td>\n"
end
end
class TableTray < Tray
def makeHTML
items = @tray.collect{|item| item.makeHTML()}.join('')
buffer = <<"EOB"
<td>
<table width="100%" border="1"><tr>
<td bgcolor="#cccccc" align="center" colspan="#{@tray.size()}">
<b>#{@caption}</b>
</td>
</tr>
<tr>
#{items}
</tr>
</table>
</td>
EOB
buffer
end
end
class TablePage < Page
def makeHTML
items = @content.collect{|item| "<tr>#{item.makeHTML()}</tr>"}.join('')
buffer = <<EOB
<html><head><title>#{@title}</title></head>
<body>
<h1>#{@title}</h1>
<table with="80%" border="3">
#{items}
</table>
<hr>
<address>#{@author}</address>
</body>
</html>
EOB
buffer
end
end
main.rb
require 'factory.rb' require 'listfactory.rb' require 'tablefactory.rb' def usage() print "Usage: ruby main.rb <class name of ConcreteFactory>\n" print "Example 1: ruby main.rb ListFactory\n" print "Example 2: ruby main.rb TableFactory\n" end ## main if ARGV.length != 1 usage() exit(0) end factory = Factory.getFactory(ARGV[0]) asahi = factory.create(:Link, "ASAHI newspaper", "http://www.asahi.com/") yomiuri = factory.create(:Link, "YOMIURI newspaper", "http://www.yomiuri.co.jp/") us_yahoo = factory.create(:Link, "Yahoo!", "http://www.yahoo.com/") jp_yahoo = factory.create(:Link, "Yahoo!Japan", "http://www.yahoo.co.jp/") excite = factory.create(:Link, "Excite", "http://www.excite.com/") google = factory.create(:Link, "Google", "http://www.google.com/") traynews = factory.create(:Tray, "Newspaper") traynews.add(asahi) traynews.add(yomiuri) trayyahoo = factory.create(:Tray, "Yahoo!") trayyahoo.add(us_yahoo) trayyahoo.add(jp_yahoo) traysearch = factory.create(:Tray, "Search Engine") traysearch.add(trayyahoo) traysearch.add(excite) traysearch.add(google) page = factory.create(:Page, "LinkPage", "YUKI, Hiroshi") page.add(traynews) page.add(traysearch) page.output()
This sample uses class instance variable as parts catalog.
factory.rb
## partsCatalog as Class Instance Variable
## DPSC p.xx
class Factory
@partsCatalog = nil
def self.partsCatalog()
@partsCatalog
end
def Factory.getFactory(klass)
begin
factory = Object.const_get(klass).new
return factory
rescue NameError
print "undefined class: #{klass}\n"
end
end
def create(part, *args)
self.class.partsCatalog[part].new(*args)
end
end
## abstract
class Item
def initialize(caption)
@caption = caption
end
end
## abstract
class Link < Item
def initialize(caption, url)
super(caption)
@url = url
end
end
## abstract
class Tray < Item
def initialize(caption)
super(caption)
@tray = Array.new()
end
def add(item)
@tray << item
end
end
## abstract
class Page
def initialize(title, author)
@title, @author = title, author
@content = Array.new()
end
def add(item)
@content << item
end
def output
begin
filename = @title + ".html";
File.open(filename, "w"){|f|
f.write(makeHTML())
}
print "#{filename} was created.\n"
rescue
print $!+"\n"
print $@.join("\n")+"\n"
end
end
def makeHTML
raise NotImplementedError
end
end
listfactory.rb
class ListLink < Link
def makeHTML()
return " <li><a href=\"#{@url}\">#{@caption}</a></li>\n";
end
end
class ListTray < Tray
def makeHTML
items = @tray.collect{|item|
item.makeHTML
}.join('')
buffer = <<EOB
<li>
#{@caption}
<ul>
#{items}
</ul>
</il>
EOB
buffer
end
end
class ListPage < Page
def makeHTML()
items = @content.collect{|item|
item.makeHTML()
}.join('')
buffer = <<EOB
<html><head><title>#{@title}</title></head>
<body>
<h1>#{@title}</h1>
<ul>
#{items}
</ul>
<hr><address>#{@author}</address>
</body></html>
EOB
buffer
end
end
class ListFactory < Factory
@partsCatalog = {
:Link => ListLink,
:Tray => ListTray,
:Page => ListPage
}
end
tablefactory.rb
class TableLink < Link
def makeHTML
"<td><a href=\"#{@url}\">#{@caption}</a></td>\n"
end
end
class TableTray < Tray
def makeHTML
items = @tray.collect{|item| item.makeHTML()}.join('')
buffer = <<"EOB"
<td>
<table width="100%" border="1"><tr>
<td bgcolor="#cccccc" align="center" colspan="#{@tray.size()}">
<b>#{@caption}</b>
</td>
</tr>
<tr>
#{items}
</tr>
</table>
</td>
EOB
buffer
end
end
class TablePage < Page
def makeHTML
items = @content.collect{|item| "<tr>#{item.makeHTML()}</tr>"}.join('')
buffer = <<EOB
<html><head><title>#{@title}</title></head>
<body>
<h1>#{@title}</h1>
<table with="80%" border="3">
#{items}
</table>
<hr>
<address>#{@author}</address>
</body>
</html>
EOB
buffer
end
end
class TableFactory < Factory
@partsCatalog = {
:Link => TableLink,
:Tray => TableTray,
:Page => TablePage,
}
end
main.rb
require 'factory.rb' require 'listfactory.rb' require 'tablefactory.rb' def usage() print "Usage: ruby main.rb <class name of ConcreteFactory>\n" print "Example 1: ruby main.rb ListFactory\n" print "Example 2: ruby main.rb TableFactory\n" end ## main if ARGV.length != 1 usage() exit(0) end factory = Factory.getFactory(ARGV[0]) asahi = factory.create(:Link, "ASAHI newspaper", "http://www.asahi.com/") yomiuri = factory.create(:Link, "YOMIURI newspaper", "http://www.yomiuri.co.jp/") us_yahoo = factory.create(:Link, "Yahoo!", "http://www.yahoo.com/") jp_yahoo = factory.create(:Link, "Yahoo!Japan", "http://www.yahoo.co.jp/") excite = factory.create(:Link, "Excite", "http://www.excite.com/") google = factory.create(:Link, "Google", "http://www.google.com/") traynews = factory.create(:Tray, "Newspaper") traynews.add(asahi) traynews.add(yomiuri) trayyahoo = factory.create(:Tray, "Yahoo!") trayyahoo.add(us_yahoo) trayyahoo.add(jp_yahoo) traysearch = factory.create(:Tray, "Search Engine") traysearch.add(trayyahoo) traysearch.add(excite) traysearch.add(google) page = factory.create(:Page, "LinkPage", "YUKI, Hiroshi") page.add(traynews) page.add(traysearch) page.output()
In this sample, there is no definition of methods and variables in subclasses of factory class. We just only define classes themselves. But you must use name their classes regularly.
For example, when you want to create some Link objects using ListFacotry and TableFactory, the name of their classes should be ListLink and TableLink.
The drawback of this method is to degrade readablity of the source code. You can add comments.
factory.rb
## Single Factory Class
## DPSC p.xx
class Factory
def Factory.getFactory(klass)
begin
factory = Object.const_get(klass).new
return factory
rescue NameError
print "undefined class: #{klass}\n"
end
end
def create(part, *args)
klassname = self.type.to_s.sub(/Factory$/, part.to_s)
Object.const_get(klassname).new(*args)
end
end
## abstract
class Item
def initialize(caption)
@caption = caption
end
end
## abstract
class Link < Item
def initialize(caption, url)
super(caption)
@url = url
end
end
## abstract
class Tray < Item
def initialize(caption)
super(caption)
@tray = Array.new()
end
def add(item)
@tray << item
end
end
## abstract
class Page
def initialize(title, author)
@title, @author = title, author
@content = Array.new()
end
def add(item)
@content << item
end
def output
begin
filename = @title + ".html";
File.open(filename, "w"){|f|
f.write(makeHTML())
}
print "#{filename} was created.\n"
rescue
print $!+"\n"
print $@.join("\n")+"\n"
end
end
def makeHTML
raise NotImplementedError
end
end
listfactory.rb
class ListFactory < Factory
end
class ListLink < Link
def makeHTML()
return " <li><a href=\"#{@url}\">#{@caption}</a></li>\n";
end
end
class ListTray < Tray
def makeHTML
items = @tray.collect{|item|
item.makeHTML
}.join('')
buffer = <<EOB
<li>
#{@caption}
<ul>
#{items}
</ul>
</il>
EOB
buffer
end
end
class ListPage < Page
def makeHTML()
items = @content.collect{|item|
item.makeHTML()
}.join('')
buffer = <<EOB
<html><head><title>#{@title}</title></head>
<body>
<h1>#{@title}</h1>
<ul>
#{items}
</ul>
<hr><address>#{@author}</address>
</body></html>
EOB
buffer
end
end
tablefactory.rb
class TableFactory < Factory
end
class TableLink < Link
def makeHTML
"<td><a href=\"#{@url}\">#{@caption}</a></td>\n"
end
end
class TableTray < Tray
def makeHTML
items = @tray.collect{|item| item.makeHTML()}.join('')
buffer = <<"EOB"
<td>
<table width="100%" border="1"><tr>
<td bgcolor="#cccccc" align="center" colspan="#{@tray.size()}">
<b>#{@caption}</b>
</td>
</tr>
<tr>
#{items}
</tr>
</table>
</td>
EOB
buffer
end
end
class TablePage < Page
def makeHTML
items = @content.collect{|item| "<tr>#{item.makeHTML()}</tr>"}.join('')
buffer = <<EOB
<html><head><title>#{@title}</title></head>
<body>
<h1>#{@title}</h1>
<table with="80%" border="3">
#{items}
</table>
<hr>
<address>#{@author}</address>
</body>
</html>
EOB
buffer
end
end
main.rb
require 'factory.rb' require 'listfactory.rb' require 'tablefactory.rb' def usage() print "Usage: ruby main.rb <class name of ConcreteFactory>\n" print "Example 1: ruby main.rb ListFactory\n" print "Example 2: ruby main.rb TableFactory\n" end ## main if ARGV.length != 1 usage() exit(0) end factory = Factory.getFactory(ARGV[0]) asahi = factory.create(:Link, "ASAHI newspaper", "http://www.asahi.com/") yomiuri = factory.create(:Link, "YOMIURI newspaper", "http://www.yomiuri.co.jp/") us_yahoo = factory.create(:Link, "Yahoo!", "http://www.yahoo.com/") jp_yahoo = factory.create(:Link, "Yahoo!Japan", "http://www.yahoo.co.jp/") excite = factory.create(:Link, "Excite", "http://www.excite.com/") google = factory.create(:Link, "Google", "http://www.google.com/") traynews = factory.create(:Tray, "Newspaper") traynews.add(asahi) traynews.add(yomiuri) trayyahoo = factory.create(:Tray, "Yahoo!") trayyahoo.add(us_yahoo) trayyahoo.add(jp_yahoo) traysearch = factory.create(:Tray, "Search Engine") traysearch.add(trayyahoo) traysearch.add(excite) traysearch.add(google) page = factory.create(:Page, "LinkPage", "YUKI, Hiroshi") page.add(traynews) page.add(traysearch) page.output()
<URL:src-abstractfactory.tar.gz>
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)