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.
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.
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:
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.
The Template design pattern is all about reusability and giving responsibilities to the user. So the objectives for this pattern are following:
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.
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:
first()
and returns the string hello
.third()
and returns the string template
.MessageRetriever
interface that has a Message() string
method.ExecuteAlgorithm
and returns the strings returned by each step joined in a single string by a space.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.
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.
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.
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
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.
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.
18.188.10.1