From: Ilmari Heikkinen Date: 2006-04-18T05:00:36+09:00 Subject: Re: [QUIZ] Refactoring (#75) On 4/14/06, Ruby Quiz 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.