I often hear people say that design patterns from the Gang of Four book are obsolete, and that in Python you don't need them at all because, well, you can just use functions. Now, I 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 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 patterns exactly as they were described in the book. It's not because the book isn't good. At the time it was published, well, basically 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 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 had like 12 people in the same building, and there was like a shared living room. And we had a shared fridge also, where we could get drinks. So, in order to keep track of all our expense, and who got which drink, and who had to pay how much, which which by the way is very Dutch way of dealing with it, I created a full like system, like an application using Modula-2 to actually keep track of those things. It was a text-based system, but it had windows, so it was drawing windows using text. And it even had like window animations. Was like really cool. It's a pity I don't have the code anymore, could have shown that here on the channel. It even had shortcuts, so if I wanted to get a beer, instead of going through the menu and opening the window, I could just type "Arjan, 1 beer", or even had like a keyboard shortcut, "A 1 B". So, that 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. So, then 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 they even used that system that I built when I 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 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 classic pattern. I'm using that all the time in my code, maybe not this version that I'm going to show you right now, but I am using another version that I'll show you afterwards. It's pretty simple. You have a class which has a method. It's 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 it 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 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 sort strategy, that contains the method that represents the particular algorithm that you're using. In this case, that's a sorting algorithm, and it 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 a BubbleSort 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 2, adding a random number, and then running a particular strategy, in this case, sorting. And then, here we're using that context with different strategies. So here, I'm sorting and manipulating this list using bubble sort, 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 `set_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 being used. That's the idea of a Strategy 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_sort` function that gets a list of data and returns a sorted list, and we also have a `quick_sort` function. And what I'm then doing is specifying a type called `SortFunction`, which is `Callable` that gets a list and 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 a class, but it's in a simple function. And then I have a `main` 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 result. Well, 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 66 65 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 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 is just one example of technique that's very handy for you to know, because it's not just helpful for writing new code, this 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. So, this is a 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 particular workshop to show you some examples of what I mean. You might even use some of these libraries in your own projects. Anyway, to check out the workshop, just go to arjan.codes/diagnosis to join for free. The 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 things in your application, and they may want to notify other objects of that change, so 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 is, 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, so that the user can see, "Hey, the payment 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 in the 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. I'm using abstract base classes here, you can also use protocols. And there's going to be an `update` method. So, an `Observer` object is going to have an `update` method, so things can update that observer, notify it of updates. Then, we're going to have concrete observers. So, these are the actual objects that are being updated. In this case, there's one class, `ConcreteObserver`. You can have many different classes, of course, as long as they inherit from `Observer`. This one just has a name, and then it implements this `update` method simply by printing something. It received a value, right? Then, we're going to have a `Subject`, which is the thing that's being observed. A `Subject` has a list of observers. And this is also where you see the decoupling happening, because even though `Subject` has a list of observers, it doesn't know anything about specific observers. It doesn't know anything about concrete observers. It just knows, "Hey, these are observers," and the only thing it knows is they have an `update` method. That's how the pattern works. And then, you have some helper methods to attach an observer and detach an 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 is going to be part of a bigger piece of code that does something, like handling a payment, or processing an order, or things like that. The `main` function is then also pretty simple. We 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 a 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. And here's an example of what that looks like. So, instead of having observer objects, we can have observer functions, like `update_observer1`, `update_observer2`. And I'm defining a type here to specify what the interface looks like of these update functions. It's simply a function that receives a string and returns `None`. 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 had here, except here we're using classes, and here we're simply using functions. And then I have a `for` loop that calls these functions with this particular data. And then, in the `main` function, I create a list of functions, `observer1`, `observer2`, and I call the `notify` function, passing this list and some data. So, this 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 observer functions, but that are called from a subject 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 used 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, a 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 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 list. I mean, you could also have a dictionary that maps 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 is that this also shows why I think that 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? That's more a choice of technology, a choice of programming approach, so to speak, that you are picking, and that might be your preference. Perhaps you like classes more than functions, or the other way around. It's all That's all totally fine. The thing is that what you're doing with this 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 classes for that, you can use functions for that, you can maybe use other things for that, subroutines if you're still coding in BASIC or something. It doesn't matter. It's about you being able to 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 detect 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 too much. And that means we need a Strategy-like pattern. So, in my opinion, what really matters is that you as a developer are able to detect these 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 occurring in code. 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 encounter a lot of them during your career. How you implement them exactly: functions, classes, something else, doesn't matter so much as long as it gets the job done. Final pattern that I want to show you is the Template Method. The idea of 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_method` method, `template_method` method, and it calls a bunch of operations. There's a simple `if` statement here that does something else. Um 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 I'm 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, there's `base_operation1`, `base_operation2`, and `base_operation3`. Then, 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 there are also, let's call them hooks, which are basically methods that you don't have to override. There are default implementations of these things, but you can replace them by something else if you want to. And then, we have a concrete class, `ConcreteTemplate1`, that implements the required operations, and in this case, it also overrides a hook. And there's another concrete class that 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 method 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 that it implements `hook2`, and not `hook1`. So, that means that this `if` statement condition will be true and will still run `base_operation3`. That's also what you see here, that's being run here, and then it calls `hook2`. Now, the second example of this Template Method, we have a concrete class two implements `hook1` and returns `False`. And that means that in the template, that `base_operation3` is not being executed. And when we look at the output, you also see this is indeed what is happening here. So, this is the idea of Template Method. 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_method` method in the previous example. But, we've set it up using a more functional style of programming. So, I have my three base operations that are simply defined. So, in the object-oriented version, they were part of this abstract class. Here they 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. And 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 some example where I have a couple of operations that I implemented. Simply print statements, doesn't matter. And I've also overwritten a hook that returns `False`, 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 just pass `operation1`, `operation2`, and then finally, I overwrite `hook1` in this particular example. And then, we get exactly the same output as we had before, in that base operation 3 is run in the first version where I don't overwrite the hook, and it's not run in the second time I'm calling this template method. 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, let 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 joining my free Discord server. It's a 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 result. And that's what I'm doing here in this variety of the Template Method. So, what I've done here is I 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_method`. And `base_template_method` just gets the required operations, and then it defines a function, a closure, `template_method`, that has the hooks. And then, it returns that particular method. And the nice thing about this, 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 to hooks. And then, you can call it several times. Here I'm calling it using the default hooks, and here I'm overriding `hook1`. So, as opposed to the previous version where I had to pass, every time I call a method, also the operations, which, you know, if you need to call it lots of times, then this can become annoying. So, using the technique of returning a function as a result, which is also something that you 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 have any tips, suggestions for how to use functions to make your code easier to read? Let me know in the comments. Now, 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 functools. So, I'm going to import `partial` to show you what I mean. What `partial` does is that it constructs a new function. It returns a function, 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 remove the operations. There we go. And now if I run this code, we're going to get exactly the same results. You see the first time we're calling base operation 3, and the second time `hook1` is overwritten, and base operation 3 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 also talked about why I think design patterns are 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 are tools that you as a developer are using to achieve the decoupling that's intended with the particular pattern. If you enjoyed this video, give it a like, because that helps YouTube spread the message. You might also like this video, which is a video where I dive deeper into functools and the things that you can do with it, because the functools package is actually really cool. Thanks for watching, and take care.