about me

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.

Categories

My Ruby Design Patterns Talk of April 14th

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 Charactersend
class Numbers Charactersend
class Others Charactersend

factory_results 
[]
%w{12345 1o1 ruby 8675309 ••• whatever OU812 ^%$#^%$}.each { |x| factory_results << CharactersFactory.new(x) }
factory_results.sort|x,yy.class.to_s <=> x.class.to_s }.reverse!
factory_results.each { |objputs "#{obj.class}: #{obj.value}" }

# produces ...

Lettersruby
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 
...

Lettersruby
Letters
whatever
Numbers
12345
Numbers
8675309
Others
: ^%$#^%$
OthersOU812
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 << selfattr_reader :classesend
  
  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 CharactersMixinend
class Numbers; include CharactersMixinend
class Others; include CharactersMixinend

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,yy.class.to_s <=> x.class.to_s }.reverse!
factory_results.each { |objputs "#{obj.class}: #{obj.value}" }
puts 
''
CharactersMixin.classes.each { |xputs "#{x}" }

# produces ...

Lettersruby
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 << selfalias_method :real_new, :new end  
  
  def self
.new(*a, &b)
    if 
match = @subclasses.detect { |klassklass.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,yy.class.to_s <=> x.class.to_s }.reverse!
factory_results.each { |objputs "#{obj.class}: #{obj.value}" }

# produces

Lettersruby
Letters
whatever
Numbers
12345
Numbers
8675309
Others
•••
Others
OU812
Others
1o1
Others
: ^%$#^%$

8 Comments

Comment #1 by Anita  on  04/20  at  12:46 AM

Nice work, CodeOfficer.

I like your refactoring.  It’s nice to see the code in various stages.

smile

Comment #2 by Toronto Condos  on  05/15  at  01:57 AM

Nice work. Thanks for the examples.

Comment #3 by blog design  on  06/19  at  01:21 AM

First time public speaking. I can tell how exciting that could be.

Comment #4 by Russ Olsen  on  06/28  at  10:44 AM

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

Comment #5 by CodeOfficer  on  06/28  at  11:50 AM

That means a lot, thanks Russ!

You just made my day smile

Comment #6 by PNC  on  06/28  at  12:25 PM

WHOOT!!  Looks like you are getting famous Mr. Officer smile

And for your first public speaking you rocked, I’m looking forward to your next show.

Comment #7 by Webmaster Services  on  07/26  at  03:11 AM

Isn’t Portland in Oregon or they have a portland in Maine too?

Comment #8 by CodeOfficer  on  07/26  at  04:18 AM

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

Leave a comment?

Name:

Email:

Location:

URL:

Remember my personal information

Notify me of follow-up comments?

Submit the word you see below:


RailsConf 2008 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.

recommend me!

Search

My Wishlists

@ Amazon.com

My Other Sites

Foundation's Edge, RJones Family, We're Not.com (only for staging), Ailee Jones (same as rjones for now)

Friends of Mine

Aaron, Barnaby, Brian, Chris, Dirk, Frank, Fred, Four, Justin, Matt, Mike, Monty, Paul, Sean, Travis

IRC Hangouts

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.