Template Design Pattern
If your code always wants to do the same thing except for a particular operation, and that operation sometimes wants to do this and sometimes wants to do that, then you probably need a template method.
To understand the template method and when we can use it, imagine that our first Ruby project is to write a program that obtains an access token from an external token generator API. The mini-program will receive a token we can use later in our program to access APIs or secure resources to get the information we need.
class Token
def access_token
response = Net::HTTP.post(url, body, headers)
json = JSON.parse(response.body)
json['access_token']
end
def url
URI('https://token-generator-endpoint')
end
def body
{
# body for the token
}
end
def headers
{ Authorization: '...' }
end
end
token = Token.new.access_token
So far, so good. It does exactly what we need.
But a new request comes in, and we need different tokens (provider, system, patient). Each of them was assigned a specific set of permissions. For example, with a system token, we can access one resource with the provider, a different one, and so on.
class Token
def initialize(token_type)
@token_type = token_type
end
def access_token
response = Net::HTTP.post(url, body, headers)
json = JSON.parse(response.body)
json['access_token']
end
def body
case @token_type
when :patient
{
# body for the patient
}
when :provider
{
# body for the provider
}
when :system
{
# body for the system
}
end
end
def url
URI('https://token-endpoint')
end
def headers
{ Authorization: '...' }
end
end
provider_token = Token.new(:provider).access_token
system_token = Token.new(:system).access_token
patient_token = Token.new(:patient).access_token
The new version might work, but the body looks a little messy, and more importantly, we’ve mixed up code that changes with code that stays the same. Thus, we violate one of the principles of good design.
Separate things that can change
I want to show how we can separate things that remain the same from those that may change. Let’s define an abstract base class with methods we don’t expect to be changed and leave the details to subclasses.
class Token
def access_token
response = Net::HTTP.post(url, body, headers)
json = JSON.parse(response.body)
json['access_token']
end
def body
raise 'Called abstract method: body'
end
def url
URI('https://token-endpoint')
end
def headers
{ Authorization: '...' }
end
end
class Provider < Token
def body
{
# body for the provider
}
end
end
class System < Token
def body
{
# body for the system
}
end
end
class Patient < Token
def body
{
# body for the patient
}
end
end
provider_token = Provider.new.access_token
system_token = System.new.access_token
patient_token = Patient.new.access_token
Summary
By having inheritance, we create classes related to each other by a common implementation. Every time we change the superclass, there is a good chance that we can change the behavior of the subclasses. If your goal is to build a system where one change doesn’t cause changes to cascade throughout the code, then we probably shouldn’t rely too much on inheritance.
Follow my other articles to see what else we can use to build robust systems.