The next pattern that we are going to analyze is called Template and it also has a lot in common with the Strategy pattern. Template consists of defining an abstract pseudo class that represents the skeleton of an algorithm where some of its steps are left undefined. Subclasses can then fill the gaps in the algorithm by implementing the missing steps, called template methods. The intent of this pattern is making it possible to define a family of classes that are all variations of a similar algorithm. The following UML diagram shows the structure that we just described:
The three concrete classes shown in the previous diagram, extend Template
and provide an implementation for templateMethod()
, which is abstract or pure virtual, to use the C++ terminology; in JavaScript this means that the method is left undefined or is assigned to a function that always throws an exception, indicating the fact that the method has to be implemented. The Template pattern can be considered more classically object-oriented than the other patterns we have seen so far, because inheritance is a core part of its implementation.
The purpose of Template and Strategy is very similar, but the main difference between the two lies in their structure and implementation. Both allow us to change some parts of an algorithm while reusing the common parts; however, while Strategy allows us to do it dynamically and possibly at runtime, with Template, the complete algorithm is determined the moment the concrete class is defined. Under these assumptions, the Template pattern might be more suitable in those circumstances where we want to create prepackaged variations of an algorithm. As always, the choice between one pattern and the other is up to the developer who has to consider the various pros and cons for each use case.
To have a better idea of the differences between Strategy and Template, let's now re-implement the Config
object that we defined in the section about the Strategy pattern, but this time using Template. Like in the previous version of the Config
object, we want to have the ability to load and save a set of configuration properties using different file formats.
Let's start by defining the template class; we will call it ConfigTemplate
:
var fs = require('fs'); var objectPath = require('object-path'); function ConfigTemplate() {} ConfigTemplate.prototype.read = function(file) { console.log('Deserializing from ' + file); this.data = this._deserialize(fs.readFileSync(file, 'utf-8')); } ConfigTemplate.prototype.save = function(file) { console.log('Serializing to ' + file); fs.writeFileSync(file, this._serialize(this.data)); } ConfigTemplate.prototype.get = function(path) { return objectPath.get(this.data, path); } ConfigTemplate.prototype.set = function(path, value) { return objectPath.set(this.data, path, value); } ConfigTemplate.prototype._serialize = function() { throw new Error('_serialize() must be implemented'); } ConfigTemplate.prototype._deserialize = function() { throw new Error('_deserialize() must be implemented'); } module.exports = ConfigTemplate;
The new ConfigTemplate
class defines two template methods: _deserialize()
and _serialize()
, that are needed to carry out the loading and saving of the configuration. The underscore at the beginning of their names indicates that they are for internal use only, an easy way to flag protected methods. Since, in JavaScript, we cannot declare a method as abstract, we simply define them as stubs, throwing an exception if they are invoked (in other words, if they are not overridden by a concrete subclass).
Let's now create a concrete class using our template, for example, one that allows us to load and save the configuration using the JSON format:
var util = require('util'); var ConfigTemplate = require('./configTemplate'); function JsonConfig() {} util.inherits(JsonConfig, ConfigTemplate); JsonConfig.prototype._deserialize = function(data) { return JSON.parse(data); }; JsonConfig.prototype._serialize = function(data) { return JSON.stringify(data, null, ' '); } module.exports = JsonConfig;
The JsonConfig
class inherits from our template, the ConfigTemplate
class, and provides a concrete implementation for the _deserialize()
and _serialize()
methods.
The JsonConfig
class can now be used as a standalone configuration object, without the need to specify a strategy for serialization and deserialization, as it is baked in the class itself:
var JsonConfig = require('./jsonConfig'); var jsonConfig = new JsonConfig(); jsonConfig.read('samples/conf.json'); jsonConfig.set('nodejs', 'design patterns'); jsonConfig.save('samples/conf_mod.json');
With minimal effort, the Template pattern allowed us to obtain a new, fully working configuration manager by reusing the logic and the interface inherited from the parent template class and providing only the implementation of a few abstract methods.
This pattern should not sound entirely new to us. We already encountered it in Chapter 3, Coding with Streams, when we were extending the different stream classes to implement our custom streams. In that context, the template methods were the _write()
, _read()
, _transform()
, or _flush()
methods, depending on the stream class that we wanted to implement. To create a new custom stream, we needed to inherit from a specific abstract stream class, providing an implementation for the template methods.