The first one uses gsub! and method chaining. Each method has its own clear responsibility and the "entry point" method, fix_all just chains the calls:
classMarkdownFixer1attr_reader:markdowndefinitialize(markdown)@markdown=markdownenddeffix_alladd_quotes_to_title().modify_hr_tags().convert_newlines().split_tags()markdownenddefadd_quotes_to_title@markdown.gsub!('title','"title"')selfenddefmodify_hr_tags@markdown.gsub!('hr','hhr')selfenddefconvert_newlines@markdown.gsub!('\r\n','\n')selfenddefsplit_tags@markdown.gsub!('tags','t a g s')selfendendputsMarkdownFixer1.new('title hr tags\r\n').fix_all
The second option is a variation on the first one, because looking at your example I see that the MarkdownFixer is a throw away object, it doesn't really need to hold state, just to transform it and return it parsed:
classMarkdownFixer2class<<selfdeffix_all(markdown)@markdown=markdownadd_quotes_to_title().modify_hr_tags().convert_newlines().split_tags()markdownenddefadd_quotes_to_title@markdown.gsub!('title','"title"')selfenddefmodify_hr_tags@markdown.gsub!('hr','hhr')selfenddefconvert_newlines@markdown.gsub!('\r\n','\n')selfenddefsplit_tags@markdown.gsub!('tags','t a g s')selfendendendputsMarkdownFixer2.fix_all('title hr tags\r\n')
The third one is "almost" functional, in the sense there's no state at all and the transformations pass down the value (especially because the order is not exactly important):
classMarkdownFixer3class<<selfdeffix_all(markdown)split_tags(convert_newlines(modify_hr_tags(add_quotes_to_title(markdown))))enddefadd_quotes_to_title(markdown)markdown.gsub('title','"title"')enddefmodify_hr_tags(markdown)markdown.gsub('hr','hhr')enddefconvert_newlines(markdown)markdown.gsub('\r\n','\n')enddefsplit_tags(markdown)markdown.gsub('tags','t a g s')endendendputsMarkdownFixer3.fix_all('title hr tags\r\n')
You might also look into the visitor pattern which is definitely overkill for this example :D
classMarkdownFixer4class<<selfdeffix_all(markdown)methods=[:add_quotes_to_title,:modify_hr_tags,:convert_newlines,:split_tags]methods.inject(markdown){|result,method|self.send(method,result)}enddefadd_quotes_to_title(markdown)markdown.gsub('title','"title"')enddefmodify_hr_tags(markdown)markdown.gsub('hr','hhr')enddefconvert_newlines(markdown)markdown.gsub('\r\n','\n')enddefsplit_tags(markdown)markdown.gsub('tags','t a g s')endendendputsMarkdownFixer4.fix_all('title hr tags\r\n')
This line methods.inject(markdown){|result, method| self.send(method, result) } calls each method in the array passing the result of the previous method the following one, with markdown as the initial value.
Hi Andy,
I thought about three variations.
The first one uses
gsub!
and method chaining. Each method has its own clear responsibility and the "entry point" method,fix_all
just chains the calls:The second option is a variation on the first one, because looking at your example I see that the MarkdownFixer is a throw away object, it doesn't really need to hold state, just to transform it and return it parsed:
The third one is "almost" functional, in the sense there's no state at all and the transformations pass down the value (especially because the order is not exactly important):
You might also look into the visitor pattern which is definitely overkill for this example :D
This is my favorite one for now :D
This line
methods.inject(markdown){|result, method| self.send(method, result) }
calls each method in the array passing the result of the previous method the following one, withmarkdown
as the initial value.Love these smaller methods.
Easy to read && easy to follow.
Learned some neat refactoring tips-- thanks!
Hmmmmm this one is pretty great! I like the use
inject
a lot. Thanks a lot for the answers! I'm going to think it over on my commute home. :)