Software design patterns are a very good way to standardize on known implementation strategies. By following design patterns you create expectations and get comfortable with the best practices. Even if you read about a design pattern and realize you have been using it for a long time, learning the formal definition will help you avoid eventual edge cases. Additionally, labeling the pattern will enhance communication, making it clearer and more effective. If you told someone about a foldable computer that you can carry around that contains an integrated trackpad, etc, you could have been more efficient by calling that a laptop.
I have already talked about design patterns in general and the decorator pattern in particular, and today I will tell you about the Template Method pattern. These templates have nothing to do with Drupal’s templates in the theme system.
Imagine that we are implementing a social media platform, and we want to support posting messages to different networks. The algorithm has several common parts for posting, but the authentication and sending of actual data are specific to each social network. This is a very good candidate for the template pattern, so we decide to create an abstract base class, Network
, and several specialized subclasses, Facebook
, Twitter
, …
In the Template Method pattern, the abstract class contains the logic for the algorithm. In this case we have several steps that are easily identifiable:
- Authentication. Before we can do any operation in the social network we need to identify the user making the post.
- Sending the data. After we have a successful authentication with the social network, we need to be able to send the array of values that the social network will turn into a post.
- Storing the proof of reception. When the social network responds to the publication request, we store the results in an entity.
The first two steps of the algorithm are very specific to each network. Facebook and Instagram may have a different authentication scheme. At the same time, Twitter and Google+ will probably have different requirements when sending data. Luckily, storing the proof of reception is going to be generic to all networks. In summary, we will have two abstract methods that will authenticate the request and send the data plus a method that will store the result of the request in an entity. More importantly, we will have the posting method that will do all the orchestration and call all these other methods.
One possible implementation of this (simplified for the sake of the example) could be:
<?php
namespace Drupal\template;
use Drupal\Component\Serialization\Json;
/**
* Class Network.
*
* @package Drupal\template
*/
abstract class Network implements NetworkInterface {
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface.
*/
protected $entityTypeManager;
/**
* Publish the data to whatever network.
*
* @param PostInterface $post
* A made up post object.
*
* @return bool
* TRUE if the post was posted correctly.
*/
public function post(PostInterface $post) {
// Authenticate before posting. Every network uses a different
// authentication method.
$this->authenticate();
// Send the post data and keep the receipt.
$receipt = $this->sendData($post->getData());
// Save the receipt in the database.
$saved = $this->storeReceipt($receipt);
return $saved == SAVED_NEW || $saved == SAVED_UPDATED;
}
/**
* Authenticates on the request before sending the post.
*
* @throws NetworkException
* If the request cannot be authenticated.
*/
abstract protected function authenticate();
/**
* Send the data to the social network.
*
* @param array $values
* The values for the publication in the network.
*
* @return array
* A receipt indicating the status of the publication in the social network.
*/
abstract protected function sendData(array $values);
/**
* Store the receipt data from the publication call.
*
* @return int
* Either SAVED_NEW or SAVED_UPDATED (core constants), depending on the operation performed.
*
* @throws NetworkException
* If the data was not accepted.
*/
protected function storeReceipt($receipt) {
if ($receipt['status'] > 399) {
// There was an error sending the data.
throw new NetworkException(sprintf(
'%s could not process the data. Receipt: %s',
get_called_class(),
Json::encode($receipt)
));
}
return $this->entityTypeManager->getStorage('network_receipts')
->create($receipt)
->save();
}
}
The post
public method shows how you can structure your posting algorithm in a very readable way, while keeping the extensibility needed to accommodate the differences between different classes. The specialized class will implement the steps (abstract methods) that make it different.
<?php
namespace Drupal\template;
/**
* Class Facebook.
*
* @package Drupal\template
*/
class Facebook extends Network {
/**
* {@inheritdoc}
*/
protected function authenticate() {
// Do the actual work to do the authentication.
}
/**
* {@inheritdoc}
*/
protected function sendData(array $values) {
// Do the actual work to send the data.
}
}
After implementing the abstract methods, you are done. You have successfully implemented the template method pattern! Now you are ready to start posting to all the social networks.
// Build the message.
$message = 'I like the new article about design patterns in the Lullabot blog!';
$post = new Post($message);
// Instantiate the network objects and publish.
$network = new \Drupal\template\Facebook();
$network->post($post);
$network = new \Drupal\template\Twitter();
$network->post($post);
As you can see, this is a behavioral pattern very useful to deal with specialization in a subclass for a generic algorithm.
To summarize, this pattern involves a parent class, the abstract class, and a subclass, called the specialized class. The abstract class implements an algorithm by calling both abstract and non-abstract methods.
- The non-abstract methods are implemented in the abstract class, and the abstract methods are the specialized steps that are subsequently handled by the subclasses. The main reason why they are declared abstract in the parent class is because the subclass handles the specialization, and the generic parent class knows nothing about how. Another reason is because PHP won’t let you instantiate an abstract class (the parent) or a class with abstract methods (the specialized classes before implementing the methods), thus forcing you to provide an implementation for the missing steps in the algorithm.
- The design pattern doesn’t define the visibility of these methods, you can declare them public or protected. If you declare these methods public, then you can surface them in an interface to make the base class abstract.
In one typical variation of the template pattern, one or more of the abstract methods are not declared abstract. Instead they are implemented in the base class to provide a sensible default. This is done when there is a shared implementation among several of the specialized classes. This is called a hook method (note that this has nothing to do with Drupal's hooks).
Coming back to our example, we know that most of the Networks use OAuth 2 as their authentication method. Therefore we can turn our abstract authenticate
method into an OAuth 2 implementation. All of the classes that use OAuth 2 will not need to worry about authentication since that will be the default. The authenticate
method will only be implemented in the specialized subclasses that differ from the common case. When we provide a default implementation for one of the (previously) abstract methods, we call that a hook method.
At this point you may be thinking that this is just OOP or basic subclassing. This is because the template pattern is very common. Quoting Wikipedia's words:
The Template Method pattern occurs frequently, at least in its simplest case, where a method calls only one abstract method, with object oriented languages. If a software writer uses a polymorphic method at all, this design pattern may be a rather natural consequence. This is because a method calling an abstract or polymorphic function is simply the reason for being of the abstract or polymorphic method.
You will find yourself in many situations when writing Drupal 8 applications and modules where the Template Method pattern will be useful. The classic example would be annotated plugins, where you have a base class, and every plugin contains the bit of logic that is specific for it.
I like the Template Method pattern because it forces you to structure your algorithm in a very clean way. At the same time it allows you to compare the subclasses very easily, since the common algorithm is contained in the parent (and abstract) class. All in all it's a good way to have variability and keep common features clean and organized.