Skip to main content
Loading Explore guides…
Design Patterns in Python · Acedra
Home
Explore
Computer Science
Design Patterns in Python
Sign in to save
Chat
If YouTube asks you to sign in, open the video on YouTube.
Watch on YouTube
Reload player
Transcript
49 segments
Copy
Hide video
0:00
I often hear people say that design patterns from the Gang of Four book are obsolete. And that's in Python, you don't need them at all. Because why you can just use functions. Now do think that in many cases, you can use functions instead of classes that the design patterns book uses. And you're going to achieve the same thing, but with simpler code. But I don't think design patterns are obsolete at all, not even in Python. And I'll show you why. To illustrate this, I'm going to look at a few
0:27
common design patterns and show you an alternative approach using functions. Now, the original design patterns book is still a good read even today. But you may not want to use these buttons exactly as they were described in a book. It's not because the book isn't good. At the time it was published. Well, best of everybody was talking about it. Just after the book was published, I started studying computer science at the University. That was actually before Java was popular. So I
0:52
was taught programming with Modula-2, which is a procedural language, it's quite similar to Pascal, actually, I was living at a student house at the time, we have like 12 people in the same building, and it was like a shared living room. And we have a shared fridge also where we could get drinks. So in order to keep track of all our expenses, and who got which drink, and who had to pay how much, which by the way, is a very Dutch way of dealing with it, I created a full like,
1:23
system, like an application using Modula-2, to actually keep track of those things. It was a text based system, but it had a window. So it was drawing windows using text. And it even had like window animations was like really cool. So pity I don't have to code anymore, could have shown that here on the channel, if even had shortcuts. So if I wanted to get a beer, instead of going through the menu and opening winning, I could just type Arjan 1 beer or even have like a keyboard
1:54
shortcut, a1b, so it was really fast to just book, a drink that you then got from the fridge. And then of course, as the evening progressed, we started to get less precise with entering the actual thing that we got sold. And at the end of the month, we always had like plenty of beers that were missing, or people that apparently consumed way more drinks than was humanly possible. So it was not ideal. But still, it worked pretty well. And I think that even use that system that I built when I
2:26
was studying for a couple of years after I left that house. So that was I guess, one of my first production software systems that I developed anyway, back to Design Patterns, I remember when design patterns was introduced in my study program, it was actually a brand new course that was set up using C++. And that was, I think, for me one of the most important courses in my software development career, I learned so much about how to use object oriented programming and how it
2:58
actually works and how to use it right. Now, today, I'm not using design patterns in the traditional sense in that way anymore, but for me, it was really an eye opener at the time. So let's take a look at a few of these design patterns and why I think they're still important. The first pattern that I want to take a look at is the strategy design pattern. It's a really a classic pattern, I'm using that all the time in my code, maybe not this version I'm going to show you right now,
3:26
but I am using another version that will show you afterwards, it's pretty simple. You have a class, which has methods. So in this case, I'm using abstract base classes to define the classes. It's an example with sorting, the aim of the strategy pattern is that allows you to replace one algorithm with something else without the system that uses the algorithm to know about it. And here's an example where I do that with sorting. Now, normally, you won't develop your own sorting
3:55
algorithms. But it's just an example to show you how it works, right. So the basic idea is that you have some sort of abstraction, an abstract class, in this case, a sorting strategy that contains the method that represents the particular algorithm that you're using. In this case, that's a sorting algorithm. And that gets data list of integers and then returns a sorted list. So this is what the strategy looks like. And then we have specific strategies. For example, we have
4:23
a bubble sort strategy, and we have a quicksort strategy. And then in a classic object oriented piece of code, you're going to have another class, let's call that context that keeps track of a particular strategy and then uses it in some sort of context. In this case, it does some random things like multiplying elements in a list of data by to having a random number and then running a particular strategy in this case sorting. And then here we using that context with different
4:54
strategies. So here I'm sourcing, and manipulating this list using bubbles. words. And here it uses quicksort. So, when you run this code, then this is what you're going to get. And here you see what the strategy allows us to do. So we can simply call strategy, a method in context class to change the strategy that's going to be used by this piece of code without this piece of code, knowing anything about the specific strategy that's been used. That's the idea of a strategy
5:22
pattern, very basic. Now, instead of using classes and inheritance, like I'm doing here, especially in Python, it makes a lot of sense to just use functions instead. And what does that look like? Well, here you see another example, the same context. So we have a bubble of sorts, function that gets a list of dates or returns a sorted list. And we also have a quickSort function. And what I'm done doing is specifying a type called the sort function, which is callable that gets a list and
5:53
returns a list, then instead of a context class, I have a context function that gets another function, the sorting function as an argument. And then that does exactly the same thing as we had in the original object oriented version of the code, except it's not in class, but it's in a simple function. And then I've made function where I simply call the context function twice, and pass it the function that it needs to use for sorting. And this gives you exactly the same results, while
6:24
not exactly the same, because it's adding random numbers and things like that. But it works in exactly the same way. But the code is way simpler, we just have about 50 lines of code, whereas in the Object Oriented version, we have 6665 lines of code. So that makes a big difference in how much code you end up with. And that's often what you see when you use functions versus classes is that with functions, you typically end up with less code, which most cases is a good thing. But
6:51
here you'll see a key technique that I'm using here to achieve this, which is that I'm passing a function as an argument to another function. So that's really one core technique. That's pretty common in functional programming, we see it a bit less in object oriented languages, but you can definitely do that in Python. And I find this extremely helpful. So that's the first technique passing a function to another function. And this just one example of technique that's
7:20
very handy for you to know, because it's not just helpful for writing new code is also helpful if you're refactoring existing code. And in general, if you want to become better at identifying problems in existing code, you might want to check out my code diagnosis workshop, which you can join for free by going to arjan.codes/diagnosis. This workshop that teaches a three factor framework to help you understand problems in the design of your code faster. I'm using existing libraries in that
7:51
particular workshop to show you some examples of what I mean, you might even use some of these libraries in your own project. Anyway, to check out the workshop just go to arjan.codes/diagnosis, to join for free link is also in the description of the video. So strategy is a pretty basic design pattern. So let's take a look at something that's a little bit more elaborate, which is the observer pattern. And with the observer pattern, the idea is that you may have objects that change
8:17
things in your application, and they may want to notify other objects of that change, or those other objects can then do something. But you still want things to be decoupled. So you don't want the thing that changes things to be coupled directly to the things that listen to those changes. Examples of where you would apply this as for example, let's say you have a system that handles payments and orders and you want to send notifications, like maybe to the user interface
8:44
so that the user can see, hey, Damon succeeded. Or you might want to send an email or an SMS or things like that. And that's typically something where the observer pattern can be very helpful. Now, what does this look like in terms of actual design and code? Well, we're going to have similar to with the strategy pattern, we're going to have an abstract class, in this case, that's the observer class is an abstract base classes here, you can also use protocols, and there's going
9:08
to be an update method. So an observer object is going to have an update methods or things can update that observer, notify it updates, then we're gonna have concrete observer. So these are the actual objects that are being updated. In this case, there's one class concrete observer, you can have many different classes. Of course, as long as they inherit from observer, this one just has a name. And then it's implements this update method, simply by printing something, it receives
9:39
the value, right? Then, we're gonna have a subject, which is the thing that's being observed. A subject has a list of observers and this also we see the decoupling happening, because even though subject has a list of observers, it doesn't know anything about specific observers doesn't know anything about concrete observer. because it just knows, Hey, these are observers. And the only thing it knows if they have an update method, that's how the pattern works. And then you have some
10:08
helper methods to attach an observer and detached observer. And then let's say this is a method to notify the observers of something. And that simply walks through the list of observers and calls the update method on each of them. And of course, you wouldn't use a subject directly like this. Normally, this would be part of a bigger piece of code that does something like handling payments or processing an order or things like that. But the main function is done. Also pretty simple. We
10:38
have subject, we create an observer, we create another observer, we attach those things to the subject, and then we call subject notify. And then this is what we get. So both observers received data. Again, this is very classic object oriented approach of dealing with this, and this is how it's described in the Gang of Four book. But you don't have to do it this way. You can also use functions. Here's an example of what that looks like. So instead of having an observer objects, we can have
11:08
observer functions like update observer one, update observer two. And I'm defining a type here to specify what the interface looks like of this update function. So it's simply a function that receives a string and returns null. And then I can have a notify function that gets a list of update functions. So that corresponds to the list of observers that we have here, except here, we're using classes. And here we're simply using functions, and then a
11:37
follow up that calls these functions with this particular data. And then, in the main function, I create a list of functions. Observe one, observer two, and I call the notify function, passing this list, add some data, so it behaves in exactly the same way as the previous version. So you see, you get exactly the same thing. But it's set up with functions, you might use some combination of this, for example, you may have observed for functions, but that are called from a subject
12:07
object, or the observers may be objects, and the subject part of the pattern might be a function, or you might go all in and it's all functions like in this particular example. So the technique I use here is very similar to what I did with the strategy pattern. So I'm passing an update function to another function. But here, I'm actually using a data structure list in combination with the function. So I have a list of functions that I'm passing as an argument. So that's another technique that
12:34
you can use. So don't just pass a function, you can pass a list of functions. But it's, of course, not limited to just using lists. I mean, you could also have a dictionary that map's event types to lists of event listeners, or, you know, you can think of lots of complex ways in which data structures and functions are combined so that they do something useful. I use this quite a lot actually, in my code. But the main point that I want to make a step, this also shows why I think that
13:03
design patterns are not obsolete. The main thing that, in my opinion happens here is that it's not so important that you're using classes, like in this example, or that you're using functions, right, it's more a choice of technology, choice of programming approach, so to speak, that you're picking and that might be your preference, perhaps you'll like classes more than functions or the other way around. So that's all totally fine. The thing is that what you're doing with this
13:30
particular design pattern is that you're detecting a particular class of problems. Namely, I have something that happens in my system. And I want to make sure that other parts of my code are aware of that and can handle it without things being horribly coupled. And you can use class for that you can use functions for that you can maybe use all the things for that subroutines. If you're still coding in basic or something, it doesn't matter, it's about you being able to
13:55
detect that this is the type of problem that we're solving. And same with strategy. It doesn't matter if you're using abstract base classes or protocols or functions or whatever. It matters that you text that the problem that you're trying to solve is an algorithm exchange issue, we want to be able to exchange one algorithm with something else without affecting the original code so much. And that means we need a strategy like pattern. So I've been in what really matters is that you,
14:27
as a developer are able to detect this problem categories and can handle them in your own preferred way. Whether this is with functions or with classes. And in my opinion, this is what design patterns are actually about. It's about groups of problems. And these are not obsolete at all. We see them still all the time. puckering can go. If you want to be a great developer, it's really important that you know these problem classes, these problem groups, because you're going to
14:55
encounter a lot of them during your career, how you implement them exact Leave functions class or something else doesn't matter so much as long as it gets the job. Final pattern, I want to show you just the template methods that if the template method is that you have a sort of standard operation standard algorithm standard way of doing things, but you want to replace parts of that process, you can see that right here. So we have a template, methods, methods, template method methods, and it
15:26
calls a bunch of operations. There's a simple if statement here that does something else. And this is the standard algorithm to do something. Of course, this is kind of meaningless code, but it's just to show you how the template method actually works. And then in that same class, here, again, using abstract base class to kind of stick to the classic way that design patterns have been conceived. There are some operations that already have an implementation. For example,
15:51
this base operation one base, Operation two and base operation three, them, we're going to have a couple of operations that you need to implement in a subclass. So if you actually want to use this template class, you're going to need to implement these two required operations. And they're also let's call them hooks, which are basically methods that you don't have to override or default implementations of these things. But you can replace them by something else if you want to.
16:23
And then we have a concrete class concrete template that implements the required operations. And in this case, it also overrides the hook. And there is no concrete class, that's also of course, has to implement the required operations and implements another hook. And then, in the main function, I create a bunch of objects and call the template methods on them. So when I run this, then this is what we get. So the first concrete class one, if we look at the code, you see
16:52
that it implements hook to a knot hook one. So that means that this if statements condition will be true, and will still run base operation three, that's also what you see here. That's being run here. And then it's called hook to. Now the second example, of this template method, we have a concrete class to implement hook one, and returns false. And that means that in the template, that base operation three is not being executed. And when we look at the output, you also see this is
17:26
indeed what is happening here. So this is the idea of template methods. But again, we don't have to use classes and inheritance for this. So here's an example where I do exactly the same thing. But I'm using functions. So I have a template method function right here that has the exact same structure as the template methods methods in the previous example, but ultimately set it up using a more functional style of programming. So I have my three base operations that are simply
17:55
defined. So in the Object Oriented version, they were part of this abstract class, here are these three methods. And we have a template method function. That's the actual template. And now of course, this has a bunch of arguments, because we need to supply it with the required operations and potentially the hooks. And that's what you see here. As you can see, I've also defined some default values for the hooks, so they are optional. This way, you don't have to provide them. And then I have
18:28
some example where I have a couple of operations that are implemented, simply print statements doesn't matter. And I also have written a hook that returns force similar to this object oriented example that I just showed you. And then in the main function, here, I'm using the default hooks, right, I've just passed operation one OR operation two. And then finally, I override hook one in this particular example, and then we get exactly the same output as we had before in that base
18:57
operation three is wrong in the first version, where I don't override the hook. And it's not run in the second time, I'm calling this template methods. So again, with functions, you can achieve, more or less the same thing as with classes. But here we use 53 lines of code. And here we use me scroll down over 70 lines of code. So that's, again, a big difference in the amount of code you have to write. By the way, if you like these types of discussions, you might also be interested in
19:26
joining my free Discord server, it's really helpful and awesome community you can join for free using the link below. What you're seeing in the functional version of the template method is that I'm relying a lot on passing functions as arguments to another function. So that's basically the same technique as I used for the strategy pattern and the observer pattern. There's one more thing you can do to spice this up by using another technique which is returning a function as a
19:52
result, and that's what I'm doing here in this variety of the template methods. So what I've done here is I've used The same template method function. So that's basically this function I had right here. But I defined a function in another function called base template methods. And base template method just gets the required operations. And then it defines a function closure template method that has the hooks, and then it returns that particular method. And nice thing about
20:24
this is, if you look at the main function is what what you can do now is you define once, the template method with the specific operations that is needed. And then template method is a function that simply gets two hooks. And then you can call it several times here I'm calling Acuson, default hooks. And here I'm overriding hook one. So as opposed to the previous version, where I had to pass every time I call the method, also the operations, which, you know, if you need to
20:53
call it lots of times undiscovered, become annoying. So using the technique of returning a function as a result, which is also something quite commonly see in functional programming, you can add a bit more flexibility to your code. Although, to be honest, this does become a bit harder to read. But here, it's a bit easier to use. I'm curious, when you write code, do you use functions in this way? Do you pass them as an argument? Do you return them as a result? Do you
21:21
have any tips or suggestions for how to use functions to make your code easier to read? Let me know in the comments. Now a little bonus tip, you don't have to do it this way, you can also do that in this version of the template method, but by using something called partial function application, and that's something really cool. That's from functors. So I'm going to import partial and show you what I mean. What partial does is that it constructs a new function, it returns a function,
21:47
but it applies already some of the arguments, which is exactly what we want to do here. So what you can do is applied template method is partial. And then we supply the template method. And we supply both the operations. And now we have a new function where these operations are already applied. So I can take this method and simply call it like this without the operations. And I can do the same thing here. So I just removed the operations. There we go. And now if I run this code,
22:19
we're going to get exactly the same results, we see the first time we're calling base operation three, and a second time hook one is overwritten, and base operation three is not called. So this partial function application basically does the same thing as what we did here with the closure, but it's a bit simpler to use. So I've showed you three different design patterns as well as functional versions of those design patterns. I've just talked about why I think design patterns are
22:45
not obsolete, because in the end, it's about the problem classes that they solve that you should know about. And it's not so much about using a very specific type of inheritance or subroutines or functions. Ultimately, those have tools that you as the developer are using to achieve the decoupling that's intended with the particular pattern. If you enjoyed this video, give us a like, because that helps the YouTube spread the message. You might also like this video,
23:11
which is video where I dive deeper into functools into things that you can do with it because the functools package is actually really cool. Thanks for watching, and take care!
Here to help you learn
Sign in to keep a private conversation with your course tutor.
Ask me anything about the material…
Sign in to chat