Template Method Pattern in Ruby
Table of Contents
When working as a software developer, knowledge of some design patterns is always welcomed. If you’ve never heard about design patterns, they are basically some general reusable patterns for common problems that developers run into. There’s a big list of these and knowing all of them is a bit hard. Well, hard might not be the right word, but it takes a lot of practice to master them all. Lets take a look at one of the (in my opinion) easier patterns - the Template Method Pattern and implement it in Ruby.
Template method pattern #
The Template method pattern (or TMP) is a design pattern that defines the program skeleton, or an algorithm skeleton in a method, and the methods that this algorithm is dependent on. The key thing is that some/all of the methods are deferred to subclasses. If this is a bit confusing, bear with me, I promise the example will clear it up. :-)
A simple payment system #
Lets say we are working on an e-commerce web application. Like every e-commerce webapp we need a payment system in place for a great user experience. Usually, the payment process has the same flow regardless of the payment provider used.
Keep in mind that this is an oversimplified example and payment systems in real web applications have more complexity around them. The flow of our example payment system is:
- Authenticate the merchant (the application) with the provider.
- Send the user’s card data to the provider with the amount of the order.
- Receive confirmation/error from the provider.
If we want to implement this payment algorithm using Stripe, it would look something like:
class StripePayment
def initialize card, amount
@api_key = ENV['STRIPE_API_KEY']
@card = card
@amount = amount
end
def process_payment!
authenticate_merchant && make_payment
end
def authenticate_merchant
begin
return true if Stripe::Merchant.authenticate @api_key
rescue Stripe::MerchantError => e
Rails.logger.error "Cannot establish connection between Merchant and Provider."
return false
rescue Stripe::ProviderUnreachable => e
Rails.logger.error "Provider unreachable."
return false
end
end
def make_payment
begin
return true if Stripe::Payment.process! @api_key, @card, @amount
rescue Stripe::PaymentUnprocessable => e
Rails.logger.error "Payment unprocessable, try again."
return false
end
end
end
Again, keep in mind, this is just dummy code, not actual Stripe payment gateway implementation. Okay, so this looks all fine. We can instantiate a new StripePayment object, pass the card object and the amount of the order as parameters in the initializer and call the process_payment! method on the object to execute the payment. For a successful payment, we need the Merchant (our web application) to successfully authenticate with the payment provider (Stripe) and then the credit card to be charged the total of the order. If any of these two fail, the payment wont be processed.
What about PayPal? #
Software is made to grow, not die. Or sometimes, get rewritten. So what if our customers need us to add PayPal as a payment option? Easy, right? We can just add another class called PaypalPayment and add the payment logic in the class. But, hold on for a second! What if we need to add Skrill as a payment too? And what if we need to add simple credit card payment, because we have customers that don’t want to register accounts with any of the aforementioned payment providers? You see, we run into a problem. We will have to add separate classes that will share a lot of the functionality. The Template Method Pattern can come into play here. So, how can we leverage TMP? We need to create a BasePayment class that will store the template method**(s)**. Then, we will create subclasses, one for every payment provider we use. In the subclasses we will add the specifics of each payment provider, while on the surface the payment will be done by calling the process_payment! method on the payment object, regardless of its class. Let’s create our BasePayment and StripePayment classes and see how we can leverage TMP.
class BasePayment
def initialize card, amount
@card = card
@amount = amount
end
def process_payment!
authenticate_merchant && make_payment
end
def authenticate_merchant
raise NotImplementedError.new "authenticate_merchant"
end
def make_payment
raise NotImplementedError.new "make_payment"
end
end
class StripePayment < BasePayment
def authenticate_merchant
begin
return true if Stripe::Merchant.authenticate ENV['STRIPE_API_KEY']
rescue Stripe::MerchantError => e
Rails.logger.error "Cannot establish connection between Merchant and Provider."
return false
rescue Stripe::ProviderUnreachable => e
Rails.logger.error "Provider unreachable."
return false
end
end
def make_payment
begin
return true if Stripe::Payment.process! ENV['STRIPE_API_KEY'], @card, @amount
rescue Stripe::PaymentUnprocessable => e
Rails.logger.error "Payment unprocessable, try again."
return false
end
end
end
As you can see, the template-method-holding-class, or BasePayment, cannot be used as a standalone class. This is because we want to use the class just as a blueprint for the subclasses, instead of instantiating any objects of it. Also, all of the BasePayment subclasses will have to implement the authenticate_merchant and make_payment methods before they can be usable. Let’s add PayPal as a payment option now!
class PaypalPayment < BasePayment
def authenticate_merchant
begin
return true if Paypal::Account.authenticate ENV['PAYPAL_API_KEY']
rescue Paypal::NotAuthenticated => e
Rails.logger.error "Cannot establish connection between Merchant and Provider."
return false
rescue Paypal::NotFound => e
Rails.logger.error "Provider unreachable."
return false
end
end
def make_payment
begin
return true if Paypal::Payment.create! ENV['PAYPAL_API_KEY'], @card, @amount
rescue Paypal::UnprocessablePayment => e
Rails.logger.error "Payment unprocessable, try again."
return false
end
end
end
As you can see, although the subclasses look alike, the logic in the template methods is very provider specific. The advantage is that the shared logic is inherited and the interfaces of all the payment classes are the same while the algorithm of the authentication and the payment is different in the subclasses. This is how TMP can be done in our example. Now, adding Skrill, or some other payment provider is really easy. Also, another advantage is that the rest of the web application can easily work with any of the payment classes.
Outro #
I hope my examples explained the Template Method design pattern. You can use the TMP when there are multiple invariants of a type in your code, and you leave placeholders in your parent type so you can easily customize the subtypes. Also, this enables the developer to not break the functionality of all the subtypes that they inherit from their super-type. Basically, you should use this pattern when you want to have a varying algorithm in a class. The template method holder class should implement the skeleton, while the subclasses should implement the details of the varying algorithm. Questions for you - in Ruby, which approach to TMP do you use? Are there more ways to achieve TMP? Or maybe you have some feedback for this post? Please share your thoughts in the comments. Thanks for reading!
Update: Updated the code to raise NotImplementedError. Thanks to David and serg-kovalev for the suggestion!