Simple Flyweight Implementation
The Flyweight design pattern can be handy for a variety of purposes (which is, of course, why it is a design pattern). I'll give a contrived example of a situation in which it might be desirable, and a simple wrapper to provide flyweight functionality. Imagine a situation in which several string formats are used around your code:
class Foo def to_s sprintf("%03d:%d", @line_num, @line.split(/\s+/).size) end end class Bar def word_count(line_num) sprintf("%03d:%d", line_num, @lines[line_num].split(/\s+/).size) end end
Please remember that this is a contrived example. The point is that similar things are being done in both methods, and it's a good candidate for refactoring. So, instead, consider:
class Formatter class << self alias :create :new end def initialize(fmt, word_count_args) @fmt = fmt @word_count_args = word_count_args end def format(*args) args = args.zip(@word_count_args).map { |val,count| count ? val.split(/\s+/).size : val } sprintf(@fmt, *args) end end class Foo def to_s fmt = Formatter.create("%03d:%d", [ false, true ]) fmt.format(@line_num, @line) end end class Bar def word_count(line_num) fmt = Formatter.create("%03d:%d", [ false, true ]) fmt.format(line_num, @lines[line_num]) end end
Now we have a situation in which a new Formatter object is created with every call to either method. Let's bring in the flyweight (classes Foo and Bar do not change):
module Flyweight def flyweight_cache(*config) (@flyweight_cache ||= {})[config] ||= yield config end end class Formatter extend Flyweight def self.create(fmt, word_count_args) flyweight_cache(fmt, word_count_args) { |format,wc_args| new(format, wc_args) } end def initialize(fmt, word_count_args) @fmt = fmt @word_count_args = word_count_args end def format(*args) args = args.zip(@word_count_args).map { |val,count| count ? val.split(/\s+/).size : val } sprintf(@fmt, *args) end end
So now only one Formatter object is ever created, and it's retrieved on each method invocation (except the first one). How simple is that? Enjoy!
Labels: Metaprogramming, Ruby