Have you ever heard Template Method pattern? We thought we know it but we might be wrong. I just found a secret, a meaning of this pattern. Template method supported by inheritance. So what is template method?
This is definition we grab somewhere on internet (lol) but it’s true.
Template Method is a behavioral design pattern that defines the skeleton of an algorithm in the superclass but lets subclasses override specific steps of the algorithm without changing its structure.
Thanks to https://refactoring.guru/ I took this picture from them.
I might want to change some steps of specific flow to get different result. However it’s still build_house method which is interface method.
By doing this we can limit the number of interface methods.
That is good! We only build sufficient interfaces. No more, no less. That makes it easy for developer to code and easier to newbie to understand the system, avoiding duplicate code (not same code but do same thing).
We wrote code to solve thing perfectly. The trouble is that we need to vary the middle of code (method or code block in general). Sometimes we need to to this, sometimes we need to do that at middle and even adding new code in future. so why don’t we separate this function to many smaller functions? The problem is that. No matters, we can do that but we need to have a main function to gather those functions and involving them continuously. However. For example.
def main_function
func1
func2
func3
end
Sometimes we want to involve func4 instead of func2. How? Furthermore, we would want to extend this function in future by making some changes at func2 (middle) position. Something worse we can do at that time:
- modifying main_function (1)
- adding if statements at position of func2. (2)
- adding yield (callback) at position of func2. (3)
(1) this is worst case we need to avoid. Modification means taking risk.
(2) this would make this function gross, not clean.
(3) this is hard to manage code. Because we might make duplicated code or even more messy. Callback code block is like the parameter. Thus, we have to provide this block every we call the function. In other hand, we might do same things at many places where we involve main_function – give the same block. So that we have template method.
Let’s firstly have a look on this code.
module HackerNewsServices
ATag = Struct.new(:title, :href)
class Crawler < Base
PATH = 'table.itemlist tr.athing td.title a.storylink'.freeze
META = {
image: 'meta[property="og:image"]',
excerpt: 'meta[property="og:description"]'
}.freeze
def latest_news(opts = {})
collect_a_tags(limit: opts[:limit]).each_with_object([]) do |a, res|
data = get(a.href)
res << HackerNew.new(title: a.title, image: image(data), excerpt: excerpt(data))
end
end
private
def image(data)
data.at(META[:image]).try(:[], 'content')
end
def excerpt(data)
data.at(META[:excerpt]).try(:[], 'content')
end
def collect_a_tags(opts = {})
data = get.css(PATH)
limit = opts[:limit] || data.size
data.take(limit).collect do |a|
ATag.new(a.text, a['href'])
end
end
def get(href = nil)
Nokogiri::HTML(open(href || url))
end
def get_src_link_preview(html)
data = Nokogiri::HTML(html)
return nil if data.css('img').empty?
data.css('img')[0]['src']
end
def get_external_content(data)
img = data.at('meta[property="og:image"]')
desc = data.at('meta[property="og:description"]')
end
end
end
We can see that there are a ton of code (private methods). By the way, we think about leaving a room for future. The code need to be easy for extending, DRY and readable. If we want to make changes, we make only smallest changes to meet requirements. This is code after refactor.
module HackerNewsServices::Crawlers
class NewsList < Base
def data(opts = {})
atags = collect_tags(limit: opts[:limit]).collect do |tag|
create_element tag
end
block_given? ? yield(atags) : atags
end
private
def create_element(tag)
raise NotImplementedError
end
def collect_tags(opts = {})
raise NotImplementedError
end
def get_html(href = nil)
Nokogiri::HTML(open(href || url))
end
end
end
These are template methods:
- create_element
- collect_tags
We are planing to do things. We put these methods into a context and allowing to implement these ones later – template, by using inheritance we have to implement these methods. If not, it would raise Exception.
Using abstract class to define template methods (abstract). Using subclasses for implementations.
However, we should consider carefully about using this pattern. Remember another pattern:
Using composition instead of inheritance – My words but same meaning.
I like to make something belongs to me. You should do that too. It’s like when we solve problems. Let’s say in our words about problem. It proves that you deeply understand it. Inheritance would increase the connections (big ball of mud).
Thank for reading!