Hello.
Welcome to the first section of our video course strategy chain of responsibility and command design
patterns in this section.
We will first start with the strategy design pattern and then move on to the chain of responsibility
design pattern.
At the end we will dive into the command design pattern.
Now let's move on to the first video of this section that deals with strategy design pattern.
In this video we're going to paint objects on a file instead of only printing text on the console.
We will have two strategies console and file but the user of the library won't have to deal with the
complexity behind them.
Now we aren't going to define structures or encapsulate object creation but we are going to deal with
behaviors wants to deal with in behavior patterns.
Well now we will encapsulate behaviors for example algorithms in the strategy pattern or executions
in the Command pattern correct behavior design is the last step.
After knowing how to deal with object creation and structures defining the behavior correctly is the
last step of good software design because all in all good software design lets us improve algorithms
and fix errors easily while the best algorithm implementation will not save us from bad software design.
Now let's talk about strategy design pattern.
So the strategy design pattern is probably the easiest to understand of the behavioral patterns.
We've used it a few times while developing the previous patterns but without stopping to talk about
it now we will the strategy pattern uses different algorithms to achieve some specific functionality.
Those algorithms are hidden behind an interface and of course they must be interchangeable.
All algorithms achieve the same functionality in a different way.
For example we could have a sort interface and a few sorting algorithms.
The result is the same.
Some list is sorted but we could have used quick sort merge sort.
And so on.
The IO dot writer interface defines a strategy to write and the functionality is always the same.
To write something we could write it to the standard out to some file or to a user defined type but
we do the same thing at the end to write.
We just changed the strategy to write.
And in this case we changed the place where we write now the object of the strategy pattern is really
clear the pattern should do these two tasks it should provide a few algorithms to achieve some specific
functionality.
Secondly all types must achieve the same functionality in a different way but the client of the strategy
shouldn't be affected.
The problem is that this definition covers a huge spectrum of possibilities.
This is because Strategy Pattern is actually used for a variety of scenarios and many software engineering
solutions come with some kind of strategy within.
Therefore it's better to see it in action with a real example.
As I said earlier we are going to do something different.
For this example instead of printing text on the console only We are also going to paint objects on
a file.
In this case we will have two strategies console and file but the user of the library won't have to
deal with the complexity behind them.
The key feature is that the caller doesn't know how the underlying library is working and he just knows
the information available on the defined strategy.
This is nicely seen on the diagram which you see on your screen.
In this diagram we have chosen to print the console but we won't deal with the console strategy type
directly will always use an interface that represents it.
The console strategy type will hide the implementation details of printing the console to color in main
function bios strategy hides its implementation details as well as any future strategy.
Now before getting our hands on some code let's understand the acceptance criteria a strategy must have
a very clear objective and we will have two ways to achieve it.
Our objectives are as listed on your screen.
The first one is provide a way to show the user an object that is square in text or image.
Next the user must choose between image or text when launching the app and then the app must be able
to add more visualization strategies like an audio for example.
And if the user selects text the word square must be printed on the console.
Lastly if the user selects image an image of a white square on a black background will be printed on
a file now let's move into the implementation.
We aren't going to write tests for this example as it will be quite complicated to check that an image
has appeared on the screen although not impossible by using open CV unimpressive library for computer
vision.
We will start directly by defining our strategy interface that each printing strategy must implement.
In our case the file and console types I have already written all the codes which we are going to use
and I am going to explain step by step.
So let's open the file image.
Dot go.
You can see that we have the type print strategy interface that's all our strategy defines a simple
print method that returns an error.
The error returning type is mandatory when dealing with files the types that need to implement print
strategy will be called console square and image Square type.
So we have named these types as console square and image square.
Now the console square struct doesn't need any in our field because it will always print the word square
to the console the image square struct will store a field for the destination of the image file where
we will print the square.
We start with the implementation of the console Square type as it is the simplest.
Next we have defined function C very easy but the image is more complex.
We won't spend too much time and explaining in detail how the image package works because the code is
easily understandable.
However here is a short explanation.
We define a size for the image width and height.
Variables of 800 pixels of width and 600 pixels of height.
Those are going to be the size limits of our image.
And anything we write outside of that size won't be visible.
The origin variables stores an image dot point a type to represent a position in any two dimensional
space.
We set the position of this point at zero comma zero the upper left corner of the image.
We need a bitmap that will represent our background.
Here we call it B G image.
We have a very handy function in the image package to create the image.
Dot are GBI types called Image dot new R GPA.
We need to pass a rectangle to this function so that it knows the bounds of the image a rectangle is
represented by two image dot point types.
It's upper left corner point which is the main field and it's lower right corner point.
That is the max field we use origins the upper left and new point with the values of width and height
as the lower right point.
The image will have a gray background color that is B G color.
This is done by instances a type of image dot uniform which represents a uniform color hence the name
the image dot the uniform type needs an instance of a color dot color interface a color dot color type
is any type that implements the RGA method that is our GP a u i n t 32 method to return a u i n t 32
value for red green blue and Alpha colors Alpha is a value for the transparency of a pixel the color
package conveniently provides a type called Color dot R GPA for this purpose when storing an image in
certain formats we have to specify the quality of the image it will affect not only the quality but
the size of the file of course.
Here it is defined as seventy five hundred is the maximum quality possible that we can set.
As you can see we are using the j peg package here to set the value of a type called options that simply
stores the value of the quality.
It doesn't have more values to apply.
Finally the draw dot print function writes the pixels on the supplied image that is g image with the
characteristics that we have defined on the bounds defined by the same image.
The first argument of the draw dot print method takes the destination image where we use BGP image.
The second argument is the bounds of the object to draw in the destination image.
We use the same bounds of the image but we could use any other if we wanted a smaller rectangle.
The third argument is the color to use to colorized the bounds the origin variable is used to tell where
the upper left corner of the bounds must be placed.
In this case the bounds of the same size as the image.
So we need to set it to the origin.
The last argument specified is the operation type.
Just leave it in the draw dot as RC argument now we have to draw the square.
The operation is essentially the same as to draw the background.
But in this case we are drawing a square over the previously drawn P.G. image.
So here's the code for it.
The square is of 200 into 200 pixels of red color.
When using the method add the rect type.
Origin is translated to the supplied point.
This is the center of the square on the image.
We create an image with the square rect and call the print function on the P.G. image again to draw
the red square over it.
Finally we create a file to store the contents of the image.
The file will be stored in the path supplied in the destination file path field of the image square
struct to create a file.
We use OS dot create that returns the OS dot file.
As with every file it must be closed after using it.
So don't forget to use the deferred keyword to ensure that you close it when the method finishes.
Let's save this file.
Cool.
Now before we proceed let's try to find an answer to this question to defer or not to defer.
Some people ask why the use of defer a tool wouldn't it be the same to simply write it without defer
the end of the function.
Well actually not if any error occurs during the method execution and you return this error.
The close method won't be executed if it's at the end of the function.
You can close the file before returning but you'll have to do it in every error check with defer.
You don't have to worry about this because the deferred function is executed always with or without
error.
This way we ensure that the file is closed.
Let's move on to the main dot go file which I have placed in the C L AI folder
to parse the arguments we use the flag package we have used it before but let's recall its usage.
A flag is a command that the user can pass when executing our app.
We can define a flag by using the flag dot type methods defined in the flag package.
We want to read the output that the user wants to use from the console.
This flag will be called output.
A flag can have a default value.
In this case it will have the value console that will be used when printing to console.
So if the user executes the program without arguments it prints to console our final step is to write
the main function.
Remember that the first thing to do in the main when using flags is to pass them using the flag dot
pass method.
It's very common to forget this step.
Here we define a variable for the strategy that the user has chosen called active strategy.
But check that the active strategy variable has the print strategy type so it can be populated with
any implementation of the print strategy variable.
We will set active strategy to new instance of text square when the user writes The hyphen hyphen output
equal to console command and an image square.
When we write the hyphen hyphen output equal to image command.
Finally here is the design pattern execution.
Our active strategy variable is a type implementing print strategy and either the text square or image
square classes.
The user will choose at runtime which strategy he wants to use for each particular case.
Also we could have written a factory method pattern to create strategies so that the strategy creation
will also be uncoupled from the main function and abstracted in a different independent package.
Think about it.
If we have the strategy creation in a different package it will also allow us to use this project as
a library and not only as a standalone app.
Now let's execute both strategies.
The text Square incident will give us a square by printing the words square on the console.
Save this file and navigate to the terminal type with the command go run main dot go hyphen hyphen output
equal to text.
So here's the output printed as square.
It has worked as expected recalling how flags work.
We have to use the double dash and the defined flag output.
In our case when you have two options using equals and immediately writing the value for the flag or
writing space and the value for the flag in this case we have defined the default value of output to
the console.
So the next three executions are equivalent which I am about to show let us execute the command go run
main dot go hyphen Ivan output space text enter so it gives the same output as the previous command
now run the third command go run main dot go again we have the same output now we have to try the file
strategy as defined before the file strategy will print a red square to a file as an image with dark
gray background so the next command which I'm about to execute accomplishes this task let's write go
run main dot go hyphen hyphen output image.
Nothing happened but everything worked correctly.
This is actually bad practice.
Users must always have some sort of feedback when using your app or your library.
Also if they are using your code as a library maybe they have a specific format for output so it won't
be nice to directly print to the console.
We will solve this issue later.
Right now so let's move to the temp folder and open the file image dot J P G.
You can see the output with a red square and a dog gray background.
Awesome.
Now let's try to solve some small issues in our library.
We have a few issues in our code.
The first one which we need to address is it cannot be used as a library.
We have critical code written in the main package that is strategy creation.
Now the solution for this issue is we need to abstract the two different packages.
The strategy creation from the command line application.
Secondly none of the strategies are doing any logging to file or console.
We must provide a way to read some logs that an external user can integrate in their logging strategies
or formats.
The solution to this one is inject an IO dot writer interface as dependency to act as a logging sink
and the last issue is that our text square class is always writing to the console and the image Square
is always writing to file.
This is to coupled for this we can inject an IO dot writer interface so that the text square and image
square can write to any of the IO dot writer implementations that are available like file and console
but also bytes buffer binary encoders Jason handlers dozens of packages.
So to use it as a library and solve the first issue we will follow a common approach in Go file structures
for apps and libraries.
First we will place our main package and function outside of the root package.
In this case in a folder called CLIA It is also common to call this folder see M.D. or even app.
So we have our main dot go file over here.
Then we have placed our print strategy interface in the root package which is now called the strategy
package.
Finally we create a shapes package in a folder with the same name where we will put both text and image
strategies.
So our file structure will be like this.
First is the root package which we have named as strategy.
It contains the file print strategy dot go and the sub package shapes within this contains files factory
dot go image dot go and text dot go and the next sub package that is CLIA contains the file main dot
go.
Now we are going to modify our interface a bit to fit the needs we have written previously.
Let's open the print strategy dot go we have added the set log Io dot right a method to add a logger
strategy to our types.
This is to provide feedback to users.
Also it has a set writer method to set the IO dot writer strategy.
This interface is going to be located on the route package in the print strategy dot go file so the
final schema looks like this.
Both the text square and image square strategies have to satisfy the set log and set writer methods
which simply store some object on their fields so instead of implementing the same twice we can create
a struct that implements them and embed this struct in the strategies.
By the way this would be the composite pattern we have seen previously.
Thus we have created a struct that includes writer and log writer both.
So now each strategy must have the print output struct embedded if we want to modify their writer and
logger fields.
We also need to modify our strategy implementation.
The text square struct now needs a field to store the output Io dot writer which is the place where
it is going to write.
Instead of writing always to the console and the log writer these two fields can be provided by embedding
the print output struct the text square struct is also stored in the file text.
Dot go within the shapes package lets open the text dot go file so the struct is now like this.
So now the print method is slightly different because instead of writing directly to the console by
using the print l end function we have to write whichever Io dot writer is stored in the writer field
let me explain to you the next part of the code the bytes dot new reader is a very useful function that
takes an array of bytes and converts them to an IO dot reader interface.
We need an IO dot reader interface to use the IO dot copy function the IO dot copy function is also
incredibly useful as it takes an IO dot reader as the second parameter and pipes it to an IO dot writer
which is its first parameter so we won't return an error in any case however it's easier to do so using
directly the right method of T dot writer.
Now let's have a look at the next lines of code.
What we have written the same function t in a little different way you can use whichever method you
like more usually you will use the right method but it's nice to know the bytes dot new reader function
too.
Did you realize that when we used t dot writer we were actually accessing print output.
Dot writer the text Square type has a right to field because the print output struct has it and it's
embedded on the text square struct.
Here's an important note for you.
Remember embedding is not inheritance.
We have embedded the print output struct on the text square struct.
Now we can access print output fields as if they were in the text square fields.
This feels a bit like inheritance but there is a very important difference here.
Text square is not a print output value but it has a print output in its composition.
What does it mean that if you have a function that expects a print output you cannot pass text square
just because it has a print output embedded.
But if you have a function that accepts an interface that the print output implements you can pass text
square.
If it has a print output embedded this is what we are doing in our example.
Let's move on to the image.
Dot go file the image square struct is now like the text square with a print output embedded the print
method also needs to be modified.
Now we aren't creating a file from the print method as it was breaking the Single Responsibility Principle
of file implements an IO dot writer.
So we will open the file outside of the image square struct and inject it on the right a field.
So we just need to modify the end of the print method where we wrote to the file.
Let's scroll down to the end of the file and here we are.
So we need to modify this part of code.
Let's do it.
Let me align the code properly.
Cool.
If you check our previous implementation after using draw you can see that we used the print method.
We created a file with OS dot to create and passed it to the JPEG dot.
Encode function.
We have deleted this part about creating the file and we have replaced it with a check.
Looking for a writer in the fields.
If I dot writer not equal to nil then on J Peg dotting code we can replace the file value we were using
previously with the content of the I dot writer field.
Finally we use Io dot copy again to log some message to the log writer.
If a logging strategy is provided we also have to abstract the knowledge needed from the user to create
instances of implementers of the print strategy for which we are going to use a factory method.
Now let us navigate to the factory dot go file.
Have a look at this code.
We have two constants one of each of our strategies.
Text strategy and image strategy.
Those are the constants that must be provided to the factory to retrieve each square drawer strategy.
Our factory method receives an argument s which is a string with one of the previous constants.
Each strategy has a print output type embedded with a default logger to s.t. these out but you can override
it later by using the set log Io dot right writer methods.
This approach could be considered a factory of prototypes if it is not a recognized strategy.
A proper message error will be returned what we have now is a library.
We have all the functionality we need between the strategy and shaped packages.
Now we will write the main package and function in a new folder called C ally.
So let's save this file and move to the CLIA folder and open the main dot go file.
You can see that we have defined the variable output.
Let me modify this code again like before the main function starts by passing the input arguments on
the console.
Together the chosen strategy.
Here we can use the variable output now to create a strategy without factory.
With this snippet we have our strategy or we stop program execution in the log.
Dot fatal method if any error is found such as an unrecognized strategy.
Now we step ahead to implement the business needs by using our library for the purpose of the text strategy.
We want to write for example to esti d out for the purpose of the image we write to image dot J peg
in the temp folder.
Just like before so we can write the code.
As I added here in the case of text strategy we use set writer to set the IO dot writer to OS dot s
t d out in the case of image strategy.
We create an image in any of our folders and pass the file variable to the set writer method.
Remember that OS dot file implements the IO dot reader and Io dot writer interfaces.
So it's perfectly legal to pass it as an IO dot writer to the set writer method.
There is something left.
Let me add the code and show you what it is.
Finally we call the print method of whichever strategy was chosen by the user and check for possible
errors.
Let's try the program now save the file and navigate to the command prompt.
Type the command go run main dot go hyphen hyphen output text it is worked as expected you can see the
output as circle what about the image strategy let's execute the command check it go run main dot go
hyphen hyphen output space image enter as you can see it returns the image is written in provided writer.
Now if you check in image dot JPEG file within the temp folder we can find our red square on the dark
background so let's move on to the temp folder and open the image dotted JPEG file and here's the output
wonderful.
In this video we have learned a powerful way to encapsulate algorithms in different struct.
We have also used embedding instead of inheritance to provide cross functionality between types which
will come in handy very often in our apps.
You'll find yourself combining strategies here and there as we have seen in the second example where
we have strategies for logging and writing by using the IO dot writer interface a strategy for byte
streaming operations in the next video.
We will explore about the chain of responsibility design pattern.