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)