I'm a 34'ish year old web application developer from South Portland, Maine. I love meeting fellow techies, drop me a line if you want to talk shop.
Posted on 04/16 at 03:53 AM
I had the honor of presenting this week at our local Ruby Users Group. This was my first time speaking publicly and it was thrilling experience. I spoke for about an hour and despite my preconceptions, survived well beyond the first 10 minutes of my talk!
The topic I chose was Design Patterns in Ruby, largely because I had just finished reading (twice) an excellent book by the same name. Design Patterns in Ruby, by Russ Olsen, was just fantastic to read. Russ’s approach was so casual, and un-reference like ... that I rarely felt lost in the patterns being described. I’ve tried a few times to read other books on the general topic of design patterns, but usually these were written for an audience of Java or C++ programmers. As well, those other books rarely did anything other than flash a few UML diagrams at you and drop you in the middle of code that was more complex than the pattern at hand, hardly an elegant way to learn a new subject. After reading Design Patterns in Ruby I actually felt a bit like Neo from the Matrix when he said ... “I know kung fu!”
Its a couple days later but I wanted to post my factory pattern examples for those who attended. My apologies for the delay, its been a busy week. The other examples I presented on were largely unchanged from examples found in the book, so I wont publish Russ’s creation. (wait, thats me!)
Thanks again to those who were able to bare with me and withstand the awkwardness of a first presentation. Kudos to Casey Rosenthal of Port Forty Nine for convincing me to get up there. I really enjoyed myself and might be convinced to do it again. Continue reading to view the example factory patterns!
I’m posting these mostly without comments for the sake brevity!
Simple inheritance factory example
# a factory outside the inheritance tree
# an abstract parent shares behavior across its children
# abstract classes in ruby dont really exist
class CharactersFactory
def CharactersFactory.new(*a, &b)
klass =
case a[0]
when /^[A-Za-z]+$/
Letters
when /^[0-9]+$/
Numbers
else
Others
end
return klass.send(:new, *a, &b)
end
end
class Characters
private_class_method :new
attr_accessor :value
def initialize(*a, &b)
@value = a[0]
end
end
class Letters < Characters; end
class Numbers < Characters; end
class Others < Characters; end
factory_results = []
%w{12345 1o1 ruby 8675309 ••• whatever OU812 ^%$#^%$}.each { |x| factory_results << CharactersFactory.new(x) }
factory_results.sort! { |x,y| y.class.to_s <=> x.class.to_s }.reverse!
factory_results.each { |obj| puts "#{obj.class}: #{obj.value}" }
# produces ...
Letters: ruby
Letters: whatever
Numbers: 12345
Numbers: 8675309
Others: •••
Others: OU812
Others: 1o1
Others: ^%$#^%$
The same factory example, but done in PHP
<?php
class CharactersFactory {
private $value;
public function __construct($value=null) { $this->value = $value; }
public function getCharacter() {
if (preg_match("/^[A-Za-z]+$/", $this->value)) {
return new Letters($this->value);
} elseif (preg_match("/^[0-9]+$/", $this->value)) {
return new Numbers($this->value);
} else {
return new Others($this->value);
}
}
}
abstract class Characters {
protected $value;
protected function __construct($value=null) { $this->setValue($value); }
public function getValue() { return $this->value; }
public function setValue($value) { $this->value = $value; }
public function __toString() {
return get_class($this).": ".$this->getValue()."\n";
}
}
class Letters extends Characters {
public function __construct($value=null) { parent::__construct($value); }
}
class Numbers extends Characters {
public function __construct($value=null) { parent::__construct($value); }
}
class Others extends Characters {
public function __construct($value=null) { parent::__construct($value); }
}
// initialize some variables
$test_characters = array('12345', '1o1', 'ruby', '8675309', '•••', 'whatever', 'OU812', '^%$#^%$');
$factory_results = array();
// build an array of factory_results from our test_characters
foreach ($test_characters as $key => $value) {
$factory = new CharactersFactory($value);
$factory_results[$key] = $factory->getCharacter();
}
// sort factory_results by its object's class names
// passes an anonymous function to usort for the sorting
usort($factory_results,
create_function(
'$a, $b',
'if (get_class($a) == get_class($b)) { return 0; } '
.'return (get_class($a) < get_class($b)) ? -1 : 1;'
)
);
// output factory_results with the Character's __toString
foreach ($factory_results as $key => $value) {
echo $factory_results[$key];
}
?>
produces ...
Letters: ruby
Letters: whatever
Numbers: 12345
Numbers: 8675309
Others: ^%$#^%$
Others: OU812
Others: 1o1
Others: •••
A more ruby-like example of my original code, using mix-ins instead the inheritance based approach
# the ruby-er way ... using mixins
# this version uses a ruby module instead of straight inheritance to share behaviors across classes
# this is a composition based approach
# it also retains its ability to track its child classes
module CharactersMixin
module ClassMethods; end
module InstanceMethods
def initialize(*a, &b)
@value = a[0]
end
end
class << self; attr_reader :classes; end
def self.included(receiver)
@classes ||= []
@classes << receiver
receiver.send :private_class_method, :new
receiver.send :attr_accessor, :value
receiver.extend ClassMethods
receiver.send :include, InstanceMethods
end
end
class Letters; include CharactersMixin; end
class Numbers; include CharactersMixin; end
class Others; include CharactersMixin; end
class CharactersFactory
def CharactersFactory.new(*a, &b)
klass =
case a[0]
when /^[A-Za-z]+$/
Letters
when /^[0-9]+$/
Numbers
else
Others
end
return klass.send(:new, *a, &b)
end
end
factory_results = []
%w{12345 1o1 ruby 8675309 ••• whatever OU812 ^%$#^%$}.each { |x| factory_results << CharactersFactory.new(x) }
factory_results.sort! { |x,y| y.class.to_s <=> x.class.to_s }.reverse!
factory_results.each { |obj| puts "#{obj.class}: #{obj.value}" }
puts ''
CharactersMixin.classes.each { |x| puts "#{x}" }
# produces ...
Letters: ruby
Letters: whatever
Numbers: 12345
Numbers: 8675309
Others: •••
Others: OU812
Others: 1o1
Others: ^%$#^%$
Letters
Numbers
Others
A MUCH more ruby-like example of my original code
# code provided by defunkt on irc.freenode.net
class Characters
attr_accessor :value
def self.matcher(regexp = nil)
@matcher ||= regexp
end
def self.inherited(subclass)
(@subclasses ||= []) << subclass
end
class << self; alias_method :real_new, :new end
def self.new(*a, &b)
if match = @subclasses.detect { |klass| klass.matcher =~ a.first }
match.real_new(*a, &b)
end
end
def initialize(*a, &b)
@value = a[0]
end
end
class Letters < Characters
matcher /^[A-Za-z]+$/
end
class Numbers < Characters
matcher /^[0-9]+$/
end
class Others < Characters
matcher /.+/
end
factory_results = []
%w{12345 1o1 ruby 8675309 ••• whatever OU812 ^%$#^%$}.each { |x| factory_results << Characters.new(x) }
factory_results.sort! { |x,y| y.class.to_s <=> x.class.to_s }.reverse!
factory_results.each { |obj| puts "#{obj.class}: #{obj.value}" }
# produces
Letters: ruby
Letters: whatever
Numbers: 12345
Numbers: 8675309
Others: •••
Others: OU812
Others: 1o1
Others: ^%$#^%$
Nice work. Thanks for the examples.
First time public speaking. I can tell how exciting that could be.
Dear Mr. Officer (Do you mind if I call you Code?)
I’m so glad you liked the book. I think your post hits an important point square on: Since Ruby opens up so many new ways of doing things beside classes and subclasses, you need to think a bit more about exactly how you are going to approach any given problem. Sometimes the traditional approach is the right thing and sometimes life gets a lot easier with procs or blocks or modules or metaprogramming. Examples like the one you provide above are great for people who are first trying to get their heads around Ruby to see what the trade offs are.
Also, I don’t know if you came across the companion web site for the book, but you might be interested in looking at http://www.designpatternsinruby.com
Finally, congratulations on your first public speaking gig. I’m sure that people have told you that it gets easier with time, so let me just add that it really is true.
Russ
That means a lot, thanks Russ!
You just made my day
WHOOT!! Looks like you are getting famous Mr. Officer
And for your first public speaking you rocked, I’m looking forward to your next show.
Isn’t Portland in Oregon or they have a portland in Maine too?
Indeed there is a Portland Maine, and its not a bad place to be!
http://maps.google.com/maps?source=ig&hl;=en&rlz;=&q;=portland%20maine&um;=1&ie;=UTF-8&sa;=N&tab;=wl
Often times I will release code for free or go that extra distance to help others online. If my skills were useful to you, please consider a small donation. Thank you very much.
Foundation's Edge, RJones Family, We're Not.com (only for staging), Ailee Jones (same as rjones for now)
Aaron, Barnaby, Brian, Chris, Dirk, Frank, Fred, Four, Justin, Matt, Mike, Monty, Paul, Sean, Travis
I can usually be found lounging on irc.freenode.net while I work, on the following channels: #codeigniter, #expression engine, #fauna, #jquery, #rubyonrails, #textmate, #werenot.
Nice work, CodeOfficer.
I like your refactoring. It’s nice to see the code in various stages.