Singletons: Only The Lonely
I’ve never really spent much time on design patterns, but recently I’ve been reading Head First Design Patterns and I can’t help but notice how much easier Ruby makes certain patterns. Composition over inheritance becomes as easy as sneezing with mixins, and the thoughtful Ruby folks have already implemented quite a few.
I don’t use Singleton as much as I should (preferring instead to just stuff all the methods in the class), but for objects which have an expensive or complicated instantiation method, it’s extremely useful. (I mean, that’s why it’s a pattern, right?) Also, the Singleton mixin takes care of all the threading complications to which Singletons often fall prey.
But effective and useful it may be, Singleton is ugly, and for one big reason: instance. I hate typing instance all over the place, and it’s been a big stumbling block in my acceptance of the Singleton mixin. I understand why it’s there–the pattern doesn’t mention moving methods from instance to class scope–but on a syntactic level it irks me. If I’m using a singleton, I really shouldn’t need to know that it is a singleton–it could be a magic happy fairy princess castle for thread-safe methods, for all I care.
So yeah, I went and did something about it. I combined the power of Singleton and Proxy. Check it out:
module Singleton
class << self
def included_with_proxy(base)
included_without_proxy(base)
base.class_eval do
class << self
def method_missing_with_proxy(m, *args)
if instance.respond_to?(m)
instance.send(m, *args)
else
method_missing_without_proxy(m, *args)
end
end
alias_method_chain :method_missing, :proxy
def methods_with_proxy
return (methods_without_proxy + instance.methods).uniq
end
alias_method_chain :methods, :proxy
def respond_to_with_proxy?(m)
return respond_to_without_proxy?(m) || instance.respond_to?(m)
end
alias_method_chain :respond_to?, :proxy
end
end
end
alias_method_chain :included, :proxy
end
end
You’ll need to require singleton and activesupport for this one, and since it uses alias_method_chain, you’ll need to either be running Rails edge or grab it from the source.
What it does is pretty simple: if it’s not already a class method, Singleton passes it on to its instance:
class Magician
include Singleton
def dove
"*poof* A dove!"
end
def segway
"*rides Segway around*"
end
end
Magician.dove #=> "*poof* A dove!"
Magician.segway #=> "*rides Segway around*"
No more instance, though it’s there if you need it.