The Template Method Pattern is a behavioral design pattern which defines the skeleton of an algorithm in a method, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithms structure.
This pattern is used to create a template for an algorithm which is basically a method which specifically has a method that defines an algorithm as a set of steps. One or more of these steps is defined to be abstract and implemented by a subclass. This ensures the algorithms structure stays unchanged, while subclasses provide some part of the implementation.
We can also add hook which is a method that is declared in the abstract class with only empty or default implementation. This gives subclasses the ability to hook into the algorithm at various points but a subclass is also free to ignore the hook.
The template pattern is also used to implement the Hollywood Principle which says “Don’t call us, we’ll call you” with the use of Template Method Pattern, we are telling subclasses, “don’t call us, we’ll call you”.
Template pattern is good when we have a use case where the structure of an algorithm is the same and we provide different implementations, we can use the template method design pattern and also its a good fit for creating frameworks.
Lets take an example in one of our project we had written an api for handling breach journey. Once the system finds out that there is a breach in elements the system will use our breach journey api to validate and pass the breach to the required downstream interfaces. We had two types of breaches DynamicBreach and a StaticBreach and the flow of the algorithm was identical with few changes specific to type of breach. Here we will use the template design pattern to define the flow of our application and we will have subclasses redefine certain steps of an algorithm without changing the algorithms structure.
Below is the class structure we will be building for this example
Lets code the example
Lets start with our abstract BreachJourney which has the template method with the breach flow
abstract class BreachJourney() { def breachJourney(breach: Breach): Unit = { val data: EnrichElementData = getEnrichData(breach.elementId); val test = isDuplicate(breach.elementId) val upgrade = isCardInUpgradeList(breach.elementId); // we are providing a hook here which can be overriden by the base classes if (isMessageRequired()) { setMessageToBreach(breach); } if (!test && !upgrade) { publish(breach, data); } } def getEnrichData(id: Int): EnrichElementData = { new EnrichElementData("port", "sap233", "HUW"); } def isDuplicate(id: Int): Boolean = { return false; } def isCardInUpgradeList(id: Int): Boolean def publish(breach: Breach, enrichElementData: EnrichElementData) def setMessageToBreach(breach: Breach) def isMessageRequired(): Boolean = { return false; } }
Lets code the concrete subclasses DynamicBreachJourney and StaticBreachJourney which has its own implementation for the methods isCardInUpgradeList,publish,setMessageToBreach and isMessageRequired
class DynamicBreachJourney extends BreachJourney { override def isCardInUpgradeList(id: Int): Boolean = { //logic specific to DynamicBreachJourney to determine whether the card is being upgraded false } override def publish(breach: Breach, enrichElementData: EnrichElementData) = { //logic specific to DynamicBreachJourney to publish the breach into the interested subscribers. } override def setMessageToBreach(breach: Breach) = { breach.message = "Breach in element id" + breach.elementId + "for metric id " + breach.metricMetadataId + "for duration type " + breach.durationType } override def isMessageRequired(): Boolean = { return true; } }
class StaticBreachJourney extends BreachJourney { override def isCardInUpgradeList(id: Int): Boolean = { //logic specific to StaticBreachJourney to determine whether the card is being upgraded false } override def publish(breach: Breach, enrichElementData: EnrichElementData) = { //logic specific to StaticBreachJourney to publish the breach into the interested subscribers. } override def setMessageToBreach(breach: Breach) = { breach.message = "Breach in element id" + breach.elementId + "for duration type " + breach.durationType } override def isMessageRequired(): Boolean = { return true; } }
Lets code the domain objects used
class Breach(var elementId:Int,var metricMetadataId:Int,var metricMetadataHourlyId:Int,var durationType:String,var durationInterval:Int,var message:String) { }
class EnrichElementData(var subelement_name: String, var element_name: String, var element_vendor: String) { }
Lets code the test class
object Test extends App { val dynamicOne = new Breach(1, 2, 3, "SW", 5, "NA"); val staticOne = new Breach(8, 9, 4, "PW", 1, "NA"); val dynamicBreach = new DynamicBreachJourney(); val staticBreach = new StaticBreachJourney(); dynamicBreach.breachJourney(dynamicOne); staticBreach.breachJourney(staticOne); }