Chapter 6. Behavioral Patterns - Template, Memento, and Interpreter Design Patterns

In this chapter, we will see the next three Behavioral design patterns. The difficulty is being raised as now we will use combinations of Structural and Creational patterns to better solve the objective of some of the Behavioral patterns.

We will start with Template design pattern, a pattern that looks very similar to the Strategy pattern but that provides greater flexibility. Memento design pattern is used in 99% of applications we use every day to achieve undo functions and transactional operations. Finally, we will write a reverse polish notation interpreter to perform simple mathematical operations.

Let's start with the Template design pattern.

Template design pattern

The Template pattern is one of those widely used patterns that are incredibly useful, especially when writing libraries and frameworks. The idea is to provide a user some way to execute code within an algorithm.

In this section, we will see how to write idiomatic Go Template patterns and see some Go source code where it's wisely used. We will write an algorithm of three steps where the second step is delegated to the user while the first and third aren't. The first and third steps on the algorithm represent the template.

Description

While with the Strategy pattern we were encapsulating algorithm implementation in different strategies, with the Template pattern we will try to achieve something similar but with just part of the algorithm.

The Template design pattern lets the user write a part of an algorithm while the rest is executed by the abstraction. This is common when creating libraries to ease in some complex task or when reusability of some algorithm is compromised by only a part of it.

Imagine, for example, that we have a long transaction of HTTP requests. We have to perform the following steps:

  1. Authenticate user.
  2. Authorize him.
  3. Retrieve some details from a database.
  4. Make some modification.
  5. Send the details back in a new request.

It wouldn't make sense to repeat steps 1 to 5 in the user's code every time he needs to modify something on the database. Instead, steps 1, 2, 3, and 5 will be abstracted in the same algorithm that receives an interface with whatever the fifth step needs to finish the transaction. It doesn't need to be a interface either, it could be a callback.

Objectives

The Template design pattern is all about reusability and giving responsibilities to the user. So the objectives for this pattern are following:

  • Defer a part of an algorithm of the library to a the user
  • Improve reusability by abstracting the parts of the code that are not common between executions

Example - a simple algorithm with a deferred step

In our first example, we are going to write an algorithm that is composed of three steps and each of them returns a message. The first and third steps are controlled by the Template and just the second step is deferred to the user.

Requirements and acceptance criteria

A brief description of what the Template pattern has to do is to define a template for an algorithm of three steps that defers the implementation of the second step to the user:

  1. Each step in the algorithm must return a string.
  2. The first step is a method called first() and returns the string hello.
  3. The third step is a method called third() and returns the string template.
  4. The second step is whatever string the user wants to return but it's defined by the MessageRetriever interface that has a Message() string method.
  5. The algorithm is executed sequentially by a method called ExecuteAlgorithm and returns the strings returned by each step joined in a single string by a space.

Unit tests for the simple algorithm

We will focus on testing the public methods only. This is a very common approach. All in all, if your private methods aren't called from some level of the public ones, they aren't called at all. We need two interfaces here, one for the Template implementors and one for the abstract step of the algorithm:

type MessageRetriever interface { 
  Message()string 
} 

type Template interface { 
   first() string 
   third() string 
   ExecuteAlgorithm(MessageRetriever) string 
} 

A Template implementor will accept a MessageRetriever interface to execute as part of its execution algorithm. We need a type that implements this interface called Template, we will call it TemplateImpl:

type TemplateImpl struct{} 
 
func (t *TemplateImpl) first() string { 
  return "" 
} 
 
func (t *TemplateImpl) third() string { 
  return "" 
} 
 
func (t *TemplateImpl) ExecuteAlgorithm(m MessageRetriever) string { 
  return "" 
} 

So our first test checks the fourth and fifth acceptance criteria. We will create the TestStruct type that implements the MessageRetriever interface returning the string world and has embedded the Template so that it can call the ExecuteAlgorithm method. It will act as the Template and the abstraction:

type TestStruct struct { 
  Template 
} 
 
func (m *TestStruct) Message() string { 
  return "world" 
} 

First, we will define the TestStruct type. In this case, the part of the algorithm deferred to us is going to return the world text. This is the string we will look for later in the test doing a check of type "is the word world present on this string?".

Take a close look, the TestStruct embeds a type called Template which represents the Template pattern of our algorithm.

When we implement the Message() method, we are implicitly implementing the MessageRetriever interface. So now we can use TestStruct type as a pointer to a MessageRetriever interface:

func TestTemplate_ExecuteAlgorithm(t *testing.T) { 
  t.Run("Using interfaces", func(t *testing.T){ 
    s := &TestStruct{} 
    res := s.ExecuteAlgorithm(s) 
   expected := "world" 
 
    if !strings.Contains(res, expected) { 
      t.Errorf("Expected string '%s' wasn't found on returned string
", expected) 
    } 
  }) 
} 

In the test, we will use the type we have just created. When we call the ExecuteAlgorithm method, we need to pass the MessageRetriever interface. As the TestStruct type also implements the MessageRetriever interface, we can pass it as an argument, but this is not mandatory, of course.

The result of the ExecuteAlgorithm method, as defined in the fifth acceptance criterion, must return a string that contains the returned value of the first() method, the returned value of TestStruct (the world string) and the returned value of the third() method separated by a space. Our implementation is on the second place; that's why we checked that a space is prefixed and suffixed on the string world.

So, if the returned string, when calling the ExecuteAlgorithm method, doesn't contain the string world, the test fails.

This is enough to make the project compile and run the tests that should fail:

go test -v .       
=== RUN   TestTemplate_ExecuteAlgorithm
=== RUN   TestTemplate_ExecuteAlgorithm/Using_interfaces
--- FAIL: TestTemplate_ExecuteAlgorithm (0.00s)
    --- FAIL: TestTemplate_ExecuteAlgorithm/Using_interfaces (0.00s)
      template_test.go:47: Expected string ' world ' was not found on returned string
FAIL
exit status 1
FAIL

Time to pass to the implementation of this pattern.

Implementing the Template pattern

As defined in the acceptance criteria, we have to return the string hello in the first() method and the string template in the third() method. That's pretty easy to implement:

type Template struct{} 
 
func (t *Template) first() string { 
  return "hello" 
} 
 
func (t *Template) third() string { 
  return "template" 
} 

With this implementation, we should be covering the second and third acceptance criteria and partially covering the first criterion (each step in the algorithm must return a string).

To cover the fifth acceptance criterion, we define an ExecuteAlgorithm method that accepts the MessageRetriever interface as argument and returns the full algorithm: a single string done by joining the strings returned by the first(), Message() string and third() methods:

func (t *Template) ExecuteAlgorithm(m MessageRetriever) string { 
  return strings.Join([]string{t.first(), m.Message(), t.third()},  " ") 
} 

The strings.Join function has the following signature:

func Join([]string,string) string 

It takes an array of strings and joins them, placing the second argument between each item in the array. In our case, we create a string array on the fly to pass it as the first argument. Then we pass a whitespace as the second argument.

With this implementation, the tests must be passing already:

go test -v . 
=== RUN   TestTemplate_ExecuteAlgorithm 
=== RUN   TestTemplate_ExecuteAlgorithm/Using_interfaces 
--- PASS: TestTemplate_ExecuteAlgorithm (0.00s) 
    --- PASS: TestTemplate_ExecuteAlgorithm/Using_interfaces (0.00s) 
PASS 
ok

The tests passed. The test has checked that the string world is present in the returned result, which is the hello world template message. The hello text was the string returned by the first() method, the world string was returned by our MessageRetriever implementation, and template was the string returned by the third() method. The whitespaces are inserted by Go's strings.Join function. But any use of the TemplateImpl.ExecuteAlgorithm type will always return "hello [something] template" in its result.

Anonymous functions

This is not the only way to achieve the Template design pattern. We can also use an anonymous function to give our implementation to the ExecuteAlgorithm method.

Let's write a test in the same method that was used previously just after the test (marked in bold):

func TestTemplate_ExecuteAlgorithm(t *testing.T) { 
  t.Run("Using interfaces", func(t *testing.T){ 
    s := &TestStruct{} 
    res := s.ExecuteAlgorithm(s) 
 
    expectedOrError(res, " world ", t) 
  }) 
 
  t.Run("Using anonymous functions", func(t *testing.T)
  {
    
     m := new(AnonymousTemplate)
    
     res := m.ExecuteAlgorithm(func() string {
      
       return "world"
    
     })
    
     expectedOrError(res, " world ", t)
  
   }) 
} 
 
func expectedOrError(res string, expected string, t *testing.T){ 
  if !strings.Contains(res, expected) { 
    t.Errorf("Expected string '%s' was not found on returned string
", expected) 
  } 
} 

Our new test is called Using anonymous functions. We have also extracted the checking on the test to an external function to reuse it in this test. We have called this function expectedOrError because it will fail with an error if the expected value isn't received.

In our test, we will create a type called AnonymousTemplate that replaces the previous Template type. The ExecuteAlgorithm method of this new type accepts the func() method string type that we can implement directly in the test to return the string world.

The AnonymousTemplate type will have the following structure:

type AnonymousTemplate struct{} 
 
func (a *AnonymousTemplate) first() string { 
  return "" 
} 
 
func (a *AnonymousTemplate) third() string { 
  return "" 
} 
 
func (a *AnonymousTemplate) ExecuteAlgorithm(f func() string) string { 
  return "" 
} 

The only difference with the Template type is that the ExecuteAlgorithm method accepts a function that returns a string instead of a MessageRetriever interface. Let's run the new test:

go test -v .
=== RUN   TestTemplate_ExecuteAlgorithm
=== RUN   TestTemplate_ExecuteAlgorithm/Using_interfaces
=== RUN   TestTemplate_ExecuteAlgorithm/Using_anonymous_functions
--- FAIL: TestTemplate_ExecuteAlgorithm (0.00s)
    --- PASS: TestTemplate_ExecuteAlgorithm/Using_interfaces (0.00s)
    --- FAIL: TestTemplate_ExecuteAlgorithm/Using_anonymous_functions (0.00s)
      template_test.go:47: Expected string ' world ' was not found on returned string
FAIL
exit status 1
FAIL

As you can read in the output of the test execution, the error is thrown on the Using anonymous functions test, which is what we were expecting. Now we will write the implementation as follows:

type AnonymousTemplate struct{} 
 
func (a *AnonymousTemplate) first() string { 
  return "hello" 
} 
 
func (a *AnonymousTemplate) third() string { 
  return "template" 
} 
 
func (a *AnonymousTemplate) ExecuteAlgorithm(f func() string) string { 
  return strings.Join([]string{a.first(), f(), a.third()}, " ") 
} 

The implementation is quite similar to the one in the Template type. However, now we have passed a function called f that we will use as the second item in the string array we used on Join function. As f is simply a function that returns a string, the only thing we need to do with it is to execute it in the proper place (the second position in the array).

Run the tests again:

go test -v .
=== RUN   TestTemplate_ExecuteAlgorithm
=== RUN   TestTemplate_ExecuteAlgorithm/Using_interfaces
=== RUN   TestTemplate_ExecuteAlgorithm/Using_anonymous_functions
--- PASS: TestTemplate_ExecuteAlgorithm (0.00s)
    --- PASS: TestTemplate_ExecuteAlgorithm/Using_interfaces (0.00s)
    --- PASS: TestTemplate_ExecuteAlgorithm/Using_anonymous_functions (0.00s)
PASS
ok

Awesome! Now we know two ways to implement the Template design pattern.

How to avoid modifications on the interface

The problem of the previous approach is that now we have two templates to maintain and we could end duplicating code. What can we do in the situation that we cannot change the interface are we using? Our interface was MessageRetriever but we want to use an anonymous function now.

Well, do you remember the Adapter design pattern? We just have to create an Adapter type that, accepting a func() string type, returns an implementation of the MessageRetriever interface. We will call this type TemplateAdapter:

type TemplateAdapter struct { 
  myFunc func() string 
} 
 
func (a *TemplateAdapter) Message() string { 
  return "" 
} 
 
func MessageRetrieverAdapter(f func() string) MessageRetriever { 
  return nil 
} 

As you can see, the TemplateAdapter type has a field called myFunc which is of type func() string. We have also defined adapter as private because it shouldn't be used without a function defined in the myFunc field. We have created a public function called the MessageRetrieverAdapter to achieve this. Our test should look more or less like this:

t.Run("Using anonymous functions adapted to an interface", func(t *testing.T){ 
  messageRetriever := MessageRetrieverAdapter(func() string { 
    return "world" 
  }) 
 
  if messageRetriever == nil { 
    t.Fatal("Can not continue with a nil MessageRetriever") 
  } 
 
  template := Template{} 
  res := template.ExecuteAlgorithm(messageRetriever) 
 
  expectedOrError(res, " world ", t) 
}) 

Look at the statement where we called the MessageRetrieverAdapter method. We passed an anonymous function as an argument defined as func() string. Then, we reuse the previously defined Template type from our first test to pass the messageRetriever variable. Finally, we checked again with the expectedOrError method. Take a look at the MessageRetrieverAdapter method, it will return a function that has nil value. If strictly following the test-driven development rules, we must do tests first and they must not pass before implementation is done. That's why we returned nil on the MessageRetrieverAdapter function.

So, let's run the tests:

go test -v .
=== RUN   TestTemplate_ExecuteAlgorithm
=== RUN   TestTemplate_ExecuteAlgorithm/Using_interfaces
=== RUN   TestTemplate_ExecuteAlgorithm/Using_anonymous_functions
=== RUN   TestTemplate_ExecuteAlgorithm/Using_anonymous_functions_adapted_to_an_interface
--- FAIL: TestTemplate_ExecuteAlgorithm (0.00s)
    --- PASS: TestTemplate_ExecuteAlgorithm/Using_interfaces (0.00s)
    --- PASS: TestTemplate_ExecuteAlgorithm/Using_anonymous_functions (0.00s)
    --- FAIL: TestTemplate_ExecuteAlgorithm/Using_anonymous_functions_adapted_to_an_interface (0.00s)
      template_test.go:39: Can not continue with a nil MessageRetriever
FAIL
exit status 1
FAIL

The test fails on line 39 of the code and it doesn't continue (again, depending on how you wrote your code, the line representing your error could be somewhere else). We stop test execution because we will need a valid MessageRetriever interface when we call the ExecuteAlgorithm method.

For the implementation of the adapter for our Template pattern, we will start with MessageRetrieverAdapter method:

func MessageRetrieverAdapter(f func() string) MessageRetriever { 
  return &adapter{myFunc: f} 
} 

It's very easy, right? You could be wondering what happens if we pass nil value for the f argument. Well, we will cover this issue by calling the myFunc function.

The adapter type is finished with this implementation:

type adapter struct { 
  myFunc func() string 
} 
 
func (a *adapter) Message() string { 
  if a.myFunc != nil { 
    return a.myFunc() 
  } 
 
  return "" 
} 

When calling the Message() function, we check that we actually have something stored in the myFunc function before calling. If nothing was stored, we return an empty string.

Now, our third implementation of the Template type, using the Adapter pattern, is done:

go test -v .
=== RUN   TestTemplate_ExecuteAlgorithm
=== RUN   TestTemplate_ExecuteAlgorithm/Using_interfaces
=== RUN   TestTemplate_ExecuteAlgorithm/Using_anonymous_functions
=== RUN   TestTemplate_ExecuteAlgorithm/Using_anonymous_functions_adapted_to_an_interface
--- PASS: TestTemplate_ExecuteAlgorithm (0.00s)
    --- PASS: TestTemplate_ExecuteAlgorithm/Using_interfaces (0.00s)
    --- PASS: TestTemplate_ExecuteAlgorithm/Using_anonymous_functions (0.00s)
    --- PASS: TestTemplate_ExecuteAlgorithm/Using_anonymous_functions_adapted_to_an_interface (0.00s)
PASS
ok

Looking for the Template pattern in Go's source code

The Sort package in Go's source code can be considered a Template implementation of a sort algorithm. As defined in the package itself, the Sort package provides primitives for sorting slices and user-defined collections.

Here, we can also find a good example of why Go authors aren't worried about implementing generics. Sorting the lists is maybe the best example of generic usage in other languages. The way that Go deals with this is very elegant too-it deals with this issue with an interface:

type Interface interface { 
  Len() int 
  Less(i, j int) bool 
  Swap(i, j int) 
} 

This is the interface for lists that need to be sorted by using the sort package. In the words of Go's authors:

"A type, typically, is a collection that satisfies sort. Interface can be sorted by the routines in this package. The methods require that the elements of the collection be enumerated by an integer index."

In other words, write a type that implements this Interface so that the Sort package can be used to sort any slice. The sorting algorithm is the template and we must define how to retrieve values in our slice.

If we peek in the sort package, we can also find an example of how to use the sorting template but we will create our own example:

package main 
 
import ( 
  "sort" 
  "fmt" 
) 
 
type MyList []int 
 
func (m MyList) Len() int { 
  return len(m) 
} 
 
func (m MyList) Swap(i, j int) { 
  m[i], m[j] = m[j], m[i] 
} 
 
func (m MyList) Less(i, j int) bool { 
  return m[i] < m[j] 
} 

First, we have done a very simple type that stores an int list. This could be any kind of list, usually a list of some kind of struct. Then we have implemented the sort.Interface interface by defining the Len, Swap, and Less methods.

Finally, the main function creates an unordered list of numbers of the MyList type:

func main() { 
  var myList MyList = []int{6,4,2,8,1} 
 
  fmt.Println(myList) 
  sort.Sort(myList) 
  fmt.Println(myList) 
} 

We print the list that we created (unordered) and then we sort it (the sort.Sort method actually modifies our variable instead of returning a new list so beware!). Finally, we print again the resulting list. The console output of this main method is the following:

go run sort_example.go 
[6 4 2 8 1]
[1 2 4 6 8]

The sort.Sort function has sorted our list in a transparent way. It has a lot of code written and delegates Len, Swap and Less methods to an interface, like we did in our template delegating to the MessageRetriever interface.

Summarizing the Template design pattern

We wanted to put a lot of focus on this pattern because it is very important when developing libraries and frameworks and allows a lot of flexibility and control to users of our library.

We have also seen again that it's very common to mix patterns to provide flexibility to the users, not only in a behavioral way but also structural. This will come very handy when working with concurrent apps where we need to restrict access to parts of our code to avoid races.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.14.136.168