top of page

Template Vs Strategy Pattern

Updated: Mar 28, 2021

Intro:

Have you ever written identical code a bazillion times?? Have you ever had to refer to an older module or a different file to reference how you did it before?


No, it doesn't ring a bell? Then get out of here...... 🚶🏻‍♂️


Well for the rest of us who have implemented quick sort plenty of times while dabbling with different programming languages, we know how that feels.


💡 It doesn't matter if you don't know what a quick sort is; that's irrelevant to learning the concepts here.


Juice:


⚠️ Only focus on the structure, ignore the language details and extra functions.

Let's start with a simple example that looks like this code snippet.


Here is a sample calculator code that I just wrote for this post. It allows the user to enter arbitrary equations, evaluates them, and print the results.


func main() {
	bufIn := bufio.NewReader(os.Stdin)
	bufOut := bufio.NewWriter(os.Stdout)
	scanner := bufio.NewScanner(bufIn)
	writer := newWriter(bufOut)

	writer("Press Q to exit\\n")
	for scanner.Scan() {
		input := scanner.Text()

		if input == "Q" || input == "q" {
			break
		}

		result := evaluate(input)
		writer("Result: " + result + "\\n")
		bufIn.Discard(-1)
	}
}
> go run . 
Press Q to exit
10 + 3
Result: 13.000
12 + 12
Result: 24.000
7 + 9
Result: 16.000
8 + 2
Result: 10.000
9 + 2 + 3
Result: 14.000
1 + 2 + 1
Result: 4.000

This is a very simple code, yet the same pattern is used in so many applications! From calculators, text processing, file parsing, etc...


The pattern is simply:

setup()
loop condition() {
	logic()
}
cleanup()

That right there 👆🏼 is literally your template! Hence, the name template pattern.


Template Pattern


"Allow a generic algorithm to manipulate many possible detailed implementations" - Uncle bob

"Generic algorithm" refers to the piece of code you want to template. In our example, it's the loop with, condition, logic and cleanup.


💡 Notice the singularity here! A single algorithm can manipulate multiple implementations.

Shhhhh SHOW ME THE CODE!

public abstract class App {
	private boolean isDone = false;
	
	protected void setDone() {
		isDone = true;
	}

	protected boolean isDone() {
		return isDone;
	}
	
	protected abstract void setup();
  	protected abstract void logic();
	protected abstract void cleanup();

	public void run() {
		setup();
    		while (!isDone())
      		  logic();
    		cleanup();
	}
}
public class Calculator extends App {
	// declare your buffers
	
	public static void main(String[] args) {
		// running the run function from App (template) class  
		(new Calculator()).run();
	}

	protected void init() {
		// assign your buffers 
	}

	protected void logic() {
		// You implement the logic here 
		// if done computing 
			// setDone()
	}

	protected void cleanup() {
		// Here you close the streams
		// and run clean up code you like
  }
}

Why show you pseudocode??

  1. When learning patterns, it is important to spot them in any language.

  2. Golang doesn't support function override. Therefore, I can't demonstrate it in Go!

  3. Most interviews will be conducted via whiteboard where you will need to use pseudocode, so master it now!


Strategy Pattern


So what is Strategy Pattern?

"Strategy pattern allows many details implementation to be manipulated by many different generic algorithms" - Uncle Bob

Strategy pattern allows you to reuse your code through the dependency inversion principle. Meaning, your code complies with an interface. Then, another class implements the algorithm utilizing that interface too.


type App interface {
	setup()
	condition() bool
	logic()
	cleanup()
}
type AppRunner struct {
	App
}

func (a *AppRunner) run() {
	a.setup()
	for a.condition() {
		a.logic()
	}
	a.cleanup()
}

Now all you need is for your code to implement the App Interface, and you are ready to execute it with any other algorithm structure you wish.


type Calculator struct{}

func (c *Calculator) setup() {
	bufIn = bufio.NewReader(os.Stdin)
	scanner = bufio.NewScanner(bufIn)
	writer = newWriter(bufio.NewWriter(os.Stdout))
	writer("Press Q to exit\\n")
	writer("Enter values\\n")
}

func (c *Calculator) condition() bool {
	return scanner.Scan()
}

func (c *Calculator) logic() {
	input := scanner.Text()

	if input == "Q" || input == "q" {
		os.Exit(0)
	}
	result := evaluate(input)
	writer("Result: " + result + "\\n")
}

func (c *Calculator) cleanup() {
	writer("Thank you for using the  most simple calc ever\\nGood bye\\n")
}


Your main function looks so elegant now!

func main() {
	calc := AppRunner{new(Calculator)}
	calc.run()
}

Say you want to run the clean up multiple times like this:

cleanup()
setup()
for condition() {
	logic()
}
cleanup()

Then all you need to do is this:

type CleanUpRunner struct {
	App
}

func (a *CleanUpRunner) run() {
	a.cleanup()
	a.setup()
	for a.condition() {
		a.logic()
	}
	a.cleanup()
}
func main() {
	calc := CleanUpRunner{new(Calculator)}
	calc.run()
}

This is particularly powerful when you want to have a pluggable system. For example, using different sort algorithms or multiple caching systems for various cases!


💡 Notice the plural “many detailed implementations" are manipulated by “many different algorithms.” This will allow decoupling; you can use the same detailed implementation and integrate it with a different setup.

Footnotes:


  • Please email, tweet, or dm me if anything requires improvement or if you would like to chat!


  • lastly thank you for reading through this blog post! You are rare and I wish you joy :cheers: 🍻



Comments


I Sometimes Send Newsletters

Thanks for submitting!

bottom of page