Implementing Undo And Redo With The Command Design Pattern

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
in this video i'm going to talk about the command design pattern you can use this pattern to represent commands and have control over when they're executed i'll show you an example of how this works and then also build on top of this example and do something that a lot of non-destructive editors also have which is nice undo and redo behavior but first let's talk about the sponsor of this video skillshare skillshare is an online learning community with thousands of inspiring classes for creators explore new skills deepen existing passions and get lost in creativity skillshare has many classes on web development programming in python software engineering and software design at the moment i'm following frank kane's course on data science and machine learning with python it's really comprehensive it contains lessons about statistics data types clustering algorithms decision trees governs libraries such as pandas basically everything you need to know in order to get started skillshare is curated for learning there are no ads and they're always launching new premium classes so you can stay focused and follow wherever your creativity takes you the first 1000 of my subscribers to click the link in the description we'll get a one month free trial of skillshare so you can start exploring your creativity today the command design pattern is a behavioral pattern that provides a way to encapsulate all knowledge about performing a certain operation into a single object here's a class diagram i'll show you how to use it to make transactions in the banking system more flexible and add undo and redo behavior to it banks are actually a great example of where you can apply the command pattern since in banks lots of the interactions are based around transactions or commands after i've implemented the command pattern and added undo and redo i'll show you one more thing that really takes the flexibility of this pattern to the next level let's dive in so we have two files here in a banking package there is an account file and a bank file it's pretty simple at the moment an account is basically your data class that contains a name a number and a balance we have a deposit and a withdraw method that basically adds and subtract something from the balance withdraw if the amount you try to withdraw more than there is in the account then you're going to get a value error and it simply adds subtracts it to the balance then a bank is basically a class that manages these accounts so there's a dictionary that contains mappings from accounts strings account names to accounts there's a create account method that gets a name and then creates an account with a random number there's a getaccount method that returns an account given the number and then there's a main file that then uses these two classes to play around with it a little bit so there's a bank we created we create a couple of accounts then account one deposits a hundred thousand cents so thousand euros and two and three as well so we all start with thousand euros then google pays our encodes 500 euros and i do that by calling both withdraw and deposit and that then becomes a transfer and then of course i run away immediately with the money so i withdraw all the money that i have and then i leave and go to a nice place in the bahamas or something and at the end i'm going to print the bank now normally of course you can't really print a bank on the other hand you know a bank prints money it's only fair that we should be able to print the bank right and then when i run this code this prints the bank you see the accounts and i don't have any money on it anymore because i left to the bahamas google has 500 euros and microsoft still has 100 000 euros so that's the situation so at the moment this code is pretty limited we have very little control over when these transactions are executed what to do when a transaction fails we can use the command pattern to make this code a lot more flexible so in a minute i'm going to change the code and show you how that works so since the core of the command pattern is that it makes transactions explicit we're going to need to create as a first step a transaction class so i'm going to go to the banking package here and then create a transaction file which will contain that particular class so that's protocol and it's a class transaction and it's going to have a single method execute doesn't provide any particular results and we don't provide an implementation because that's not needed here so then this becomes our very simple transaction class so now what we can do is create a couple of common transactions so i'm going to add another file here called commands which is going to contain those commands and i'll do three there's a deposit a withdrawal and a transfer so let's start with the deposit so let's also make these things a data class because i think that's quite helpful here and we're going to have a class deposit and deposit is going to have an account and it's going to have an amount that we're going to deposit into that account i need to fix a few of the inputs here for some reason this is not importing automatically i'm not sure why it doesn't do that and let's also import the account class there we go so we have our deposit class that has an account and an amount then we are going to implement the method that we need if we want to define a transaction which is the execute execute and what that's going to do is self.account.deposit the amount one thing we probably want to do is print out some status information so i'm going to add a property that just gives me the transfer details so i can use it in various places later on and let's just create an f string here and that's going to print the amount divide by 100 because we're using integers that represent dollar cents so like so to account self dot account dot name stats are the transfer details and then here i'm just going to print deposited self dots transfer details i'm going to create a very similar class for the withdrawal so i'll just copy this over withdrawal and it also gets an account and an amount just like the deposit this transfer detail thing we can leave it and this is going to be a with draw and then we print out the message as well and then finally create a transfer class and this one is going to be a little bit different it's going to have from account and this is going to have a 2 account and an amount we also want a property here for the transfer details that's going to provide us with a string and this is going to be an f-string that prints the amount from account to account and then this is what we get doesn't entirely fit on the screen but you get the gist of what this does and then of course transfer also needs an execute function so we first withdraw the money from the from account and then we're going to deposit that money to the to account and then let's print out some status information transferred transfer details and probably here i should not call this transfer details that actually doesn't make a lot of sense so um i should probably call this transaction details and let's do that here as well transaction details and let's do the same thing here there we go so we have a deposit a withdrawal and a transfer these are the basic transactions the basic commands that we're going to need in a bank the next thing we'll need is a kind of controller class that allows us to execute these commands and that controller cast and uses the bank to execute the transactions so i'm going to add another file here called controller and the controller let's again make that a data class i still don't get automatic imports not sure why there we go and that's a class bank controller for the moment doesn't do that much but we're going to expand it in a few minutes so this also has an execute function method sorry that gets a transaction that's of type transaction which we're going to manually import for some reason from banking imports sorry dot transaction imports transaction there we go and then what do we do in this execute function well for now we're just going to execute the transaction at the moment this doesn't make a lot of sense but because later on i'm going to add undo and redo operations you'll see why this is very useful to have a separate controller class like this so that's the bank controller so now let's change the main file to actually use these transactions instead of directly depositing and withdrawing money so we're creating a bank so this we still need to do but what we also need to do is create a controller so create a bank controller so here we do have automatic imports not sure what's happening so we have a bank controller we still need to create a few accounts obviously but now let's use the transactions i'm going to execute a deposit which is let's say to account one we're going to deposit a hundred thousand and deposit okay deposit it can import automatically all right deposit and we're also going to need a withdrawal and a transfer because i also want to play around with that so we have our deposit here of a thousand euros and then let's copy this a few times there we go and then i'm going to make this account two and a count three so that basically replaces these lines and then we can also do a transfer we're going to execute a transfer let's see from account two to account one and with an amount oh i just write the amount here there we go and then i just delete these here it's actually useful to define keyword arguments so we're sure that account two is the from account and account one is the to account so we're not accidentally transferring any money to google we wouldn't want that to happen obviously from accounts that's account two and then we also need to change that here obviously to two accounts and this is going to be the amount so now we have a few deposits we have a transfer and let's also do a withdrawal just for fun there we go and we're still going to print the bank afterwards just because we can and now let's run this and well we get the same thing except now we also have a few of these status messages so we're depositing money to various account with transferring money and we're withdrawing money i should probably add a dollar sign there where is my transfer yeah i forgot that here try that one more time yeah that looks much better i've now built a basic application that uses the command pattern so we have the transaction protocol class and we have classes that implement that protocol the next thing i'm going to do is add undo and redo behavior to this example so let's see how that works in order to implement undo and redo we need to extend the transaction protocol to allow for this because at the moment it only has an execute method and we also want to define what undo should do and what redo should do and it makes sense to make this a part of the transaction because at that level we know exactly what we've done so we can also probably hopefully undo it so let's add here two methods an undo method and the redo method which of course we're also not going to provide any implementation here because that's what we'll do in the actual transaction classes so this is step one of what we're going to need now of course we also need to add undo and redo to our commands that we defined because otherwise they won't implement the protocol correctly so let's define what undo and redo should do in terms of the deposit i see i forgot to add type hints here so in case of undoing a deposit that means we just have to withdraw the money again and let's also print that we did this undid deposit of self self.transaction details that's undo and let's also implement redo in this case redo is actually the same as execute but still i'm going to write it here explicitly because sometimes you want to do something a bit different in the redo phase because something has changed so let's add it here again deposit that amount there we go and let's also print that read it deposit of the transaction details there we go this is what our deposit class now looks like i'm going to copy this to the withdraw class and then obviously change it because now it's doing exactly the opposite of what i wanted to do though so the undo in the case of withdrawal is that we deposit the money again and the redo means we need to withdraw it there we go in case of the transfer the undo and redo was slightly more complicated but not that much i think i missed one more type hint here there we go and in the case of transfer let's add these methods as well so we have a undo method so undo simply means that from the to account we withdraw the money again and the from accounts we deposited there we go let's also print out a status message undid transfer transaction details and redo so you could maybe simplify this and just say redo is just execute and undo is the other way around i just split it here because i think it might be useful in some cases so here this is again it's the same thing amounts which we withdraw and then to the to account we deposit it and let's print the status okay so now we defined what should happen if we undo a command or transaction and what should happen when we redo a transaction what we still need to fix now is have some way of managing undos and redos and that's where the bank control class comes into play because instead of just executing a transaction the responsibility of the bank controller is to keep track of this undo redo list so that it can basically roll back those transactions and go to a previous state so that means in case of the bank controller we're going to need two lists to keep track of this one is the undo stack which is a list of transactions and i'll use the default factory for this which is a list and field i need to automatically import there we go and we also going to need a redo stack and we need to keep track of these two lists because we need to know what we should do when we undo something or what we should do when we redo something so we have undo and redo stack and now that means that when we execute a transaction we're going to need to update these two stacks with the right information the first thing we need to do is when you execute a new transaction it means the redo stack gets cleared because you can't redo things from the past after you've done a new transaction so we'll say the redo stack dot clear so it gets reset to an empty stack basically and then what we do is the self.undo stack we're going to append the transaction so we can undo it then what i'm going to do is add an undo method to the bank control that just undoes the last transaction undo the type is going to be none i should probably also add that here there we go if there is nothing in the undo stack we can't do anything here so i'll just add an if statement here to cover that case then the first step is that we're going to need to get the last transaction that we executed and undo that transaction so the last one we can get using the pop method so let's say we have a transaction variable and that's the dot undo stack dot pop what's nice about pop is that it also now removes it from the undo stack which is great because we're going to undo this transaction so no longer should be in the undo stack so take the last element remove it and then we have our transaction and we're going to undo this transaction transaction dot undo in order to allow for redo behavior we should also add it now to the redo stack so we can redo anything that we've just undone so let's add the transaction there so this is what undo does the redo method does something similar we do if there is no redo stack we simply return because we can't do anything then we're going to get the last transaction from the redo stack using exactly the same pop method so that again removes it from the redo stack and then we execute the transaction by calling redo and then finally we add the transaction to the undo stack so now we have in the controller our execute our undo and our redo now before i continue let's just verify that this still works correctly so i'm still printing the bank and getting all the information here but now let's say i want to undo my withdrawal at the end so you see currently if i print it back my balance is zero but if i add one line here and say controller dot undo nudu undo there we go and let's run this code now you see my balance is back to 1500 euros you can play around with this let's let's say i'm going to undo the deposit to the microsoft account so then you see microsoft doesn't have any money and then let's redo this and then you see we are back at the full balance because it redid the transaction again if i call redo one more time actually nothing is going to happen because there is nothing in the redo stack anymore as you can see so it's it doesn't have any effects here you could change this to actually raise an error or something if you wanted to but the idea is the same we now have pretty neat system where we can deal with transactions and undo and redo them because a command or transaction is now a thing we can make it even more flexible by adding groups or batches of commands that seamlessly integrate into this system so let's see how that works so let's create one more type of command which is a batch which basically accepts a list of commands i'm going to add it here to the commands file that's also a data class so it's a batch it has a list of commands this time so using the default factory here of list that's a transaction and that's the field there we go so batch keeps track of a list of commands and then we also have an execute method because obviously it needs to implement the protocol when you execute a list of commands i'd like to roll back to the previous state before we started executing the batch basically so that means i need to put my execute calls on the commands in the list in a try accept block so i can catch any value errors that occur so what i'm going to do in order to be able to roll back is keep track of the commands that have successfully completed so i'll make a variable here called completed commands which is also a list of transaction initially this is empty there we go and then create a try accept lock and in that block i'm just going to go through each of these commands and i'm going to call execute on the command there we go and then i'll add the commands to the completed commands list if we have an exception so let's say we want to deal with value error you could add multiple types of exceptions here if you wanted to but i'll just deal with value errors here then we're going to have to undo the commands that were already completed and because we have to do the undo we have to go back in time basically we have to start at the end of the list and then go back to the front and do them again in that order so i need to reverse my completed commands list and i'm going to call undo on each of these commands so that's our execute function so that's the execute method i also need to add to the batch and undo and the redo method so if i'm undoing the commands i need to do the same thing basically what i did here with the completed commands but now i need to do it with all the commands so also need the reversed list or that's actually not a list what you get but an iterator and that's going to get the self. command so it's going to iterate through the command list in a from back to front and then i'm going to call command dot undo and redo is the same thing and here i'm assuming that undo and redo because we did those operations before are not going to raise an error i mean it's it's a big assumption that's probably not a very smart idea you probably want to also add exceptions here but it becomes complicated very quickly because what do you do when an undo fails you need to undo the undo and probably at that point you want to send a mail to a bank operator and ask what you should do now because it's not clear anymore i see i have an arrow here that's because i wrote the name wrong and then here i'm also going to go through the list so for redoing things i don't need to reverse the list so we're redoing the command so again you might simplify this example by just assuming that redo does the same thing as execute so now we have the batch class we can start using it in the main function so let's say i have here my initial deposit of a thousand euros to the rem codes account what i can do now is also execute a batch of commands so this is going to get a batch where i'm going to define what the list of commands is so that's a list of a couple of things so let's say i want to move these things into a batch so i'll deposit all this money and then i'll also do this transfer here i should add a comma here like so and then i can remove this so now i've created a batch of commands and let me run this and see if this works as intended so i didn't remove this transfer i should probably do that and these ones i can remove as well so now we get the same behavior as before i added the batch so yeah that looks good we have the withdrawal i undid the redraw actually and these google and microsoft balances also seem correct so the funny thing is that let me put this into comments if i run this now so we have the batch so actually now the only thing that happens was uh depositing this money and transferring it from the second account to the first account so then we get this state but i can now do an undo and that's actually going to undo the entire batch so you see now i have my original money again google and microsoft have zero which is as it should be but you see undo basically does undoes the entire batch and of course what's cool is that batch is a command so you can create a batch containing other batches of commands etc etc and that really shows the flexibility of the command pattern because a command is now a thing that you can store that you can organize a structure in some way you can even create a system where the the time and dates that these batches are executed is not immediate they're planned and then for example you could prepare a whole batch of transactions that you're going to do as a bank and then execute them at night when all your servers are not so busy with the day-to-day operations of your bank so that's just one of the nice things you can do with the command pattern and let's also check that rolling back works so now i undid it and basically what we end up with is that my original balance is a thousand euros let's comment this out and now let's say i try to withdraw from account three which is the microsoft account i'm going to withdraw a ridiculous amount that doesn't exist on that account the balance is not there so this is going to raise a value error and then what's going to happen is it's going to roll back these deposits as well because it's going to roll back the entire batch so let's try this so now you see we have the original deposit to my account but google both microsoft are zero which is what we like this transfer has also not been executed because the withdrawal here resulted in an error and then it undid these things so you can see it actually undid these things here because the error happens it might be helpful to actually print the arrow when there isn't such an exception so let's do that let's say we're going to print command error and then we're just going to print the value error there we go as e and then this is going to be e there we go and now if i run the main function one more time oh there's something wrong here wait let's try that again so now you see we get a comma there are insufficient fonts as you can see the command pattern is quite powerful you can use it to model transactions in a banking system like i did in this example but this pattern is also used in a lot of non-destructive editing programs like video or audio editors what i didn't really talk about in this video is that next to storing the state is that you can also store the transaction history of course you want to know what has been withdrawn and deposited onto your bank account right but there's also some redundancy there because if you know that the history of transactions you can basically reconstruct the balance that's on your account so then the question rises what is the ground truth is the truth is that the balance on your account or is the truth the history of transactions in the example that i've shown today clearly the balance is the ground truth because we don't store the list of transactions next week i'm going to revisit this pattern and then i'll switch the implementation to use transactions as the ground truth and not the state and see how that affects the design of the system i hope you enjoyed this video if you did give it a like and consider subscribing to my channel if you want to watch more of my content thanks for watching take care and i hope to see you next week for part two
Info
Channel: ArjanCodes
Views: 18,225
Rating: undefined out of 5
Keywords: command design pattern, design patterns, command pattern, design pattern, design pattern tutorial, design patterns tutorial, design patterns in software engineering, command design pattern python, command design pattern real world example, design patterns tutorial python, design patterns tutorial teacher, undo redo, command design pattern undo redo, command design pattern explained, Command design pattern real time example
Id: FM71_a3txTo
Channel Id: undefined
Length: 32min 25sec (1945 seconds)
Published: Fri Nov 12 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.