On 4/14/06, Ruby Quiz <james / grayproductions.net> wrote:> The three rules of Ruby Quiz:>> 1. Please do not post any solutions or spoiler discussion for this quiz until> 48 hours have passed from the time on this message.>> 2. Support Ruby Quiz by submitting ideas as often as you can:>> http://www.rubyquiz.com/>> 3. Enjoy!>> Suggestion: A [QUIZ] in the subject of emails about the problem helps everyone> on Ruby Talk follow the discussion.>> -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=>> by Pat Eyler>> This week's quiz is a bit of a departure from the normal. Instead of submitting> different implementations of the same code, we'd like you to submit different> implementations of the same process -- Refactoring.>> Refactoring is the art of improving the design of existing code, without> changing it's functional behaviour. It is well documented in the book> 'Refactoring' by Martin Fowler, and on his website:>> http://www.refactoring.com>> The quiz this week is to submit refactorings of code you use -- whether your own> code, a library or application for RubyForge, or even something from the> Standard Library. Any submission should implement a refactoring from:>> http://www.refactoring.com/catalog/index.html>> or other citable sources, e.g.:>> http://kallokain.blogspot.com/2006/01/refactoring-extract-mixin.html>> Each Submission should follow this outline:>> Refactoring name (and citation if needed)> Original code> Explanation of the purpose and mechanics of the refactoring> New code> (optionally, unit tests created/used to verify the code)>> Submissions will be combined into an online catalog of Ruby Refactorings,> probably on the RubyGarden wiki.>>
Extract method calls (http://www.theserverside.com/articles/article.tss?l=AspectOrientedRefactoringPart1)
Consider a cache that needs to be kept up to date.For example, primitives in a complex vector scene:redrawing the scene takes a relatively long time,degrading framerate, but not redrawing the scenewhen something changes is even worse. So I needto detect when the cached rendering is outdated.
To do this, I keep track of the time of the lastrendering, and compare the vector object mtimesto it. If a mtime is more recent than the last rendertime, the scene needs to be redrawn.
Original code:
class VectorObject attr_reader :x, :y, :z, :path, :stroke, :fill, :mtime
def x= x @x = x @mtime = Time.now end
def y= y @y = y @mtime = Time.now end
def z= z @z = z @mtime = Time.now end
def path= pt case pt when Path @path = pt when Array @path = Path.new(pt) end @mtime = Time.now end
... and so on for stroke and fill
end
Detecting repetition, it makes code fragile and a pain to edit.First pass, refactor `@mtime = Time.now` out to a method:
class VectorObject attr_reader :x, :y, :z, :path, :stroke, :fill, :mtime def touch! @mtime = Time.now end
def x= x @x = x touch! end
... similarly for other accessorsend
A bit better. Still quite unwieldy though.But! Metaprogramming to the rescue!Alias the original methods to _non_touching andreplace them with a version that calls the originalmethod, followed by touch!, and returns what theoriginal method returned:
class VectorObject def self.touching! *mnames mnames.each{|mn| alias_method "#{mn}_non_touching", mn define_method(mn){|*args| rv = __send__ "#{mn}_non_touching", *args touch! rv } } end
attr_reader :mname attr_accessor :x, :y, :z, :path, :stroke, :fill touching! :x=, :y=, :z=, :path=, :stroke=, :fill=end
Finally, move #touching! out from the class and into a module:
module Touchable def touching! *mnames mnames.each{|mn| alias_method "#{mn}_non_touching", mn define_method(mn){|*args| rv = __send__ "#{mn}_non_touching", *args touch! rv } endend
class VectorObjectextend Touchable attr_reader :mname attr_accessor :x, :y, :z, :path, :stroke, :fill
def path= pt case pt when Path @path = pt when Array @path = Path.new(pt) end end
touching! :x=, :y=, :z=, :path=, :stroke=, :fill=end
Now we can use attr_accessor for creatingthe simple methods, override #path= witha magical setter, and not type touch! a lot.