Go Design Patterns – Generator Pattern and Observer Pattern – Part Two

hey guys my name is tensor welcome back to the golang design pattern tutorial series in today's video we're going to be covering two patterns we'll be covering the generator pattern and then we will look at the observer pattern so first let's start with the generated pattern so a generator is a special function that is used to control the iteration behavior of a loop in fact all generators are technically iterators if you've ever worked at Python or dart you'll be used to the term generator in Python for instance a generator is a function that doesn't have a return keyword but rather uses the yield keyword in dart on the other hand you have the star async generator which allows you to create a stream in girl generators make use of channels and so they can either be synchronous or asynchronous and so ultimately a generator is just a function but it behaves like an iterator alright so let's go ahead and create a generator function here and I'm going to use the Fibonacci algorithm for this so we'll create a function called Fibonacci and it'll take in one single integer and then it will return a channel of integer type so we want to go ahead and make our channel of integer type so we just say make Chan int and this will be called out and then of course we're going to return this channel and inside of this function will have another anonymous function running inside of this anonymous function we'll create a for loop which essentially uses what is in essence a list comprehension to generate our Fibonacci sequence which will look like this and if the Fibonacci sequence starts with 0 and 1 we then add those two together to get one and then we add the next two values together to get two and then we repeat that pattern and so in our for loop we take 0 we put it into AI we then take 1 and we put it into J then we have a condition here that says we want to continue the loop so long as I is less than n we move forward and we take I and J which would be 0 and 1 add them together put them into I and then we put 0 into J so then for the next iteration I is 1 J is 0 we add those together that gives us another one for I and of course because the last I was one that gets put into J so now we have one one for I and J so then these two get added together and we get two for I our original one gets put into J so then that gives us 3 and then 5 and then 8 and 13 and so on and of course we're only really concerned about I as far as output goes so we're going to take the I value and pipe it into our out channel now keep in mind that the execution of these two functions will happen asynchronously fib will execute will create the channel and then we'll spawn a go routine which has this logic inside of it which will run the for loop and while that's happening we'll pass the out channel back to the main function and so this outer function will end but this go routine will continue executing so now we can go ahead down to our main function and use the fib function like an iterator and because we're receiving a channel from our fib function we can use a for loop to iterate through the sequence of values that are coming from our generator function so we can just say for X in range fib 10 million and we'll then print out each of the X values as we get them from our out channel now we can go into our terminal and execute our program and you'll see here that we will get our Fibonacci sequence we do have an error here and I've deliberately made a mistake to show you guys this error so the error tells us that all of the girl routines are asleep in that we've hit a deadlock what essentially is happening here is that our channel is still open and so the for loop here is expecting to get another value from the channel because it's still open and as a result it throws a deadlock so the program doesn't really get the chance to exit proper and so we need to close the channel after our for-loop finishes so inside of our anonymous function we just want to called defer close and then pass in our channel which will then close our channel which will then close our channel when the anonymous function finishes its execution and that's it this is a generator function that we've created here and it generates a Fibonacci sequence that is less than the value of n that we input into the function and of course you could use this pattern to generate really any kind of sequence of data all right so now that we've finished the generator pattern let's go ahead and take a look at the observer pattern and we're actually going to use our generator function to demonstrate the observer pattern in the observer pattern we create a object or a data structure which we can call a subject this subject maintains a list of other objects which wants to know about what's happening with this object and we can call these other objects observers and the idea is that as the main object changes as the subject changes it will automatically notify them of these state changes you'll be familiar with the observer pattern if you've ever used any rx libraries or reactive X libraries in reactive X you have observables and you have subjects the subjects are the part that actually implements the observer pattern in this way where they contain some kind of state and then they also contain a list of observers that are listening to the subject all right so let's go ahead and define a bunch of different types first let's define an event type the event type will be a data type that we're going to send to our observers when our Fibonacci generator generates a new Fibonacci number we want to attach to this struct a piece of data which will be an integer and this will essentially just be the integer that's going through our channel now along with our event we want to create an interface for our observer type and the observer type we'll implement a notify callback function which will just take in an event and then we can use this to define the logic for what the observer will do when it's notified of an event well create another interface here which we'll call subject and the subject will be the item that we're listening to and of course we want the subject to also maintain a list of the objects that are listening to it we can add three functions to our subject we'll have an add listener function which will take in an observer type then we'll have a remove listener function which were also taken an observer type and then we'll have the notify function which will take in the event type and of course notify will be called when we get some kind of event that we want to then notify our observers with now we can go and create a struct for each of our observers so each of our observers will have an ID and then they'll also maintain a bit of time and so essentially what we'll do is we'll be able to find how long it takes the FIB function to produce a new value and finally we'll create an event subject struct which will contain a sync map which will contain a list of the observers that are listening to this event subject all right so now we need to implement our interfaces for each of our struct types so we need to implement the observer interface for the event observer struct type so that it will be an observer type and then we need to implement the subject interface for the event subject type so that it will be a subject type for our event observer we'll implement the notify callback function and notify callback takes in an event this function of course is the callback function that gets executed when we get notified of an event and so what we want to do is just print out that we've received the data on the event after a certain amount of time and we can get our time by calling time dot sense on e dot time which is just our event observer field we can go ahead and implement add listener and remove listener for our event subject and of course these take in the observer type for the first one all we're going to do is just take the observer type store it into our map and then start with an empty anonymous struct as its value then with the removing listener method we'll take in the observer and then we'll call ask dot observers delete on that observer to delete it from the map if an observer is in our map then it's a listener if it's not in our map then it will not be a listener the notify method will be a little bit more complicated than the others because we want to add logic which will allow us to iterate through the map of observers and then pass an event to each of those observers the sync map type has a method on it called range and we can use this method to iterate through all of the keys and values inside of the map now this range function takes in a function and the function will have the key which is an interface type the value which is also an interface site and then it will return a boolean if it returns true then it will continue to iterate and if it returns false then it will stop iterating through the map and so we can set up a little if statement to just check to see if the key itself is nil and if that key doesn't exist then we'll return false and so what this means is that it will just iterate through the values that exist in the map and then once it hits the end of that list it'll have a empty key and then it will just return false which will stop the iteration and I made a mistake up here I was asking an observer in as a parameter the notify function needs to take in an event here after we've checked to see if the key is nil we can then take the key and then execute the notify callback method on that key which is the observer of course and then we can pass in the event that we want to pass to this observer now of course this is going to throw an error because the key is currently an interface type and so we need to take the key and cast it as an observer and we can do it by just saying keyed out observer in parentheses like this dot notify callback and we'll then just return true like this that way the iteration will continue until we hit an empty key we can also go ahead and expand this little if checked and we can just say if he is equal to nil or the value is equal to narrow then we'll return false either way it should be all right you can come down to our main function and set things up so that we use the observer pattern so first we can go ahead and create our event subject which will contain our observers map we can then set up our observers I'm just going to create two of them and we'll just have observer of ID 1 and then observer of ID 2 and then both of these will also have their time fields just be timed on now so they'll just get the time stamp from when they were created we can then take these observers and put them into our ad listener method on our event subject so n dot add listener and then we pass in the reference for observer 1 and then a reference for observer 2 and then finally we can have a for loop like we had before which will iterate through our generator function and then call and notify and create a new event with data and then the data of course will be the x value in our for loop so now we can go ahead and run our program we'll get each of our values twice because both of our observers are receiving the values each time a value is created by our generator so we get 0 0 and then we get 1 1 and then 1 1 again then 2 2 and then 3 3 and 5 5 and so on and this will just keep doing and actually to make this a little bit clearer let's go ahead and just add the observer ID to the print function here so we'll just say observer ID so observer 1 received the value after the time and so of course now we can see observer 2 received 0 after 0 seconds and then one receives 0 after 1 point 2 4 milliseconds and then this will just keep going of course the order that the observers receive the events is not guaranteed because this is all asynchronous in this case our observer 2 got the 0 event first and then observer 1 received that event and if we go all the way down to the end you can see that observer one received this value after 32 milliseconds whereas observer 2 received that same value after 33 milliseconds so even the time will change in some cases as well now finally let's add some logic to remove one of our observers after a certain period of time above our for loop we can create a anonymous function on a go routine and of course executed immediately and inside of this function we'll just have a select block which will have a single case and the case will check for a channel that will send a piece of data after 10 milliseconds and when that piece of data comes through we'll call n dot remove listener on our first observer which will then remove it from our map and then should stop it from getting notifications and now if we run our application if we scroll up to where 10 milliseconds happens you can see that the first observer stops receiving data and the second observer continues to receive data all right guys well I hope you enjoyed this tutorial if you did feel free to like and subscribe if you have any questions or comments feel free to leave them in the box below and if you dislike this video and then by all means download it as much as you like if you'd like to catch the next video in the golang design pattern tutorial series and you should go ahead and click that notification bill have a good night

Tags: , , , , , , , , , , , , , , , , , , , , , , ,


  • Ahmed Elbashiry says:

    very useful . thank you so much man

  • Taufiq Rahman says:

    Very informative video! :')
    Btw, you meant type assertion and not casting(type conversion) which is used for such situations where you had to wrap your concrete type into an interface and want your concrete type back. (This info might help others so putting it here.)

  • Farhan Yousaf says:

    Thank you for the videos on Go!

Leave a Reply

Your email address will not be published. Required fields are marked *