This tutorial on the observer pattern is the fourth part of my series on how to write better Python code, I show you how to use events to write code that's really easy to change and extend. Events are a variant of the observer / listener design pattern. This video is inspired by a real-life example I recently encountered, where implementing an event-based approach provided a great solution to a design problem I had. Next to showing how to use events in your code, you'll also see what the effect is on the cohesion and coupling of the functions you write.
All parts in this series:
Part 1: Cohesion and coupling - https://youtu.be/eiDyK_ofPPM
Part 2: Dependency inversion - https://youtu.be/Kv5jhbSkqLE
Part 3: The strategy pattern - https://youtu.be/WQ8bNdxREHU
Part 4: The observer pattern - https://youtu.be/oNalXg67XEE
Part 5: Unit testing and code coverage - https://youtu.be/jmP3fp_BhmE
Part 6: Template method and bridge - https://youtu.be/t0mCrXHsLbI
Part 7: Exception handling - https://youtu.be/ZsvftkbbrR0
Part 7b: Monadic error handling - https://youtu.be/J-HWmoTKhC8
Part 8: Software architecture - https://youtu.be/ihtIcGkTFBU
Part 9: SOLID principles - https://youtu.be/pTB30aXS77U
Part 10: Object creation patterns - https://youtu.be/Rm4JP7JfsKY
💡Here's my FREE 7-step guide to help you consistently design great software: https://arjancodes.com/designguide.
Relevant books:
- Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides: https://amzn.to/3jllgyH
- Principles of Package Design: Creating Reusable Software Components by Matthias Noback: https://amzn.to/2NETK3l
- Clean Code: A Handbook of Agile Software Craftsmanship by Robert Martin: https://amzn.to/3qVZgNs
- The original Design Principles and Design Patterns article by Robert Martin: https://fi.ort.edu.uy/innovaportal/file/2032/1/design_principles.pdf
🎓 Courses:
The Software Designer Mindset: https://www.arjancodes.com/mindset
The Software Designer Mindset Team Packages: https://www.arjancodes.com/sas
The Software Architect Mindset: Pre-register now! https://www.arjancodes.com/architect
Next Level Python: Become a Python Expert: https://www.arjancodes.com/next-level-python
The 30-Day Design Challenge: https://www.arjancodes.com/30ddc
🛒 GEAR & RECOMMENDED BOOKS: https://kit.co/arjancodes.
You can find the code I worked on in this episode in my GitHub repository: https://github.com/arjancodes/betterpython
🔖 Chapters:
0:00 Intro
1:24 Explaining the code example
3:16 Analysis
4:45 Creating a simple event handler
7:19 Moving to an event-based approach
10:40 The complete solution
12:44 The power of an event-based system
14:12 Final thoughts
👍 If you enjoyed this content, give this video a like. If you want to watch more of my upcoming videos, consider subscribing to my channel!
DISCLAIMER - The links in this description might be affiliate links. If you purchase a product or service through one of those links, I may receive a small commission. There is no additional charge to you. Thanks for supporting my channel so I can continue to provide you with free content each week!
in this video i'm going to show you how to use
the observer pattern to create better separation between various modules in your code this pattern
allows you to do some really cool things i'm going to show you an example the observer pattern
also called the listener sometimes is actually a classic from the gang of four book on design
patterns here's a class diagram the important thing to note is that there are two roles the
subject and the observer the subject does things and changes things
and notifies observers of any
changes that happened there's several variations of this pattern in this video i'm actually
not going to implement exactly the classical object-oriented version of it but you commonly
see it in event management systems and this is also the way that most developers use this pattern
handle user interface or general system events this is a pity because the pattern can be very
powerful and work for you and i'm going to show you how to use that to your advantage in
a real
world example suppose you write a back-end api and you work on a register user function next to
the database operations this function also sends a message to slack to the sales team it sends
a welcome email to the user and it writes a log to a server somewhere now it's possible that you
write a function that looks something like this that's not good let's write something better let's first go over the structure of this example
the example is a little bit more involved than the previo
us ones because i wanted to show something
that kind of approaches the situation like you would encounter in real life the example that
i have here imports a few functions from an api folder and it does a few things it registers
a user it sends a password reset message and upgrades the plan of the user now normally these
things would be api calls i just put them right here in the code just for testing purposes if you
look at the api folder we have a user file and a plan found these actually
contain the methods that
i was calling just there so in user for example we have to register new user which takes a name
password and email address and we have a password forgotten function that takes an email address so
if you look at the body there's one part which is the actual job that it needs to do like creating a
user and then it posts a few messages like here it posts a slack message so the sales department
can spam the person it sends a welcome mail it writes something to the serv
er log etc etc and
these things are all imported from a lib folder at the moment this doesn't really do anything
like for example this send email message doesn't actually send an email but it simply prints the
message to the console similar for log and for slack in a real application obviously this would
be integrated with the actual services that you'd be using the database is the most complicated of
all these things in the sense that it's also not a real database but it creates an array o
f users it
has a class user that has name password email and a few other things and then it has a few functions
so you can create a user and you can find the user so these are things that you'd normally have
in a database driver but here in this example obviously we're just doing that locally now the
problem with this code is that if you look at for example register new user you see it's doing a lot
of different things it's overall a method that has pretty weak cohesion not only does it nee
d to know
about database stuff it also needs to know about slack email logs etc now because register new user
needs to know about all these particular functions it means you also have a lot of different imports
here and similar for password forgotten so it also does the actual job it needs to do which is find
the user and generate a password reset code and then it sends a message it writes something to the
server etc etc and for the part of the application dealing with upgrading the user's
plan it actually
looks pretty similar part of this job is doing the changes in the database which is finding the user
and then upgrading the plan in the database but then it also posts a slack message sends an email
writes something to the log and you see also here there's a lot of different imports that means that
upgrade plan just like register new user also has pretty weak cohesion and is directly coupled
with all these very particular implementations so that sounds very nice and we want
to change
that and this is where the observer pattern or specifically an event system can really help you
separate these things so let's try to rewrite this application so that it uses events instead of
these direct couplings between the code and the messaging python has several event libraries
but just to show how it works i'm going to write my own it's it's actually pretty simple
so let's add an event file here event dot pi and basically the event system is very simple
you have a number
of subscribers that subscribe to different types of events and whenever you
post an event you need to notify the subscribers so let's create first a dictionary containing
the subscribers now we have a dictionary and then each item in the dictionary is going
to be for that particular type of event the list of subscribers that need to
be notified when that event happens so let's add a subscribe
method that does that for us so we're giving it an event type which is
basically just a string an
d a function that needs to be called i'm not going to use
type hints here for the function because frankly in python type hints for functions are
a complete mess the only thing we have to do when we subscribe to a particular event type
is first check that there is already a list of that event type in the dictionary if not we
need to create one and then add it to the list so if not already in the
subscriber list then let's create a list of subscribers for this particular event and we're dire
ctly appending the
function that needs to be called to the subscriber list now again there are already
event library so probably you won't have to write this yourself i'm just adding it so it shows
you how the pattern actually works and then next to subscribing we also need to be able to post
events so let's add a function for that as well and normally when you post an event you
generally want to pass along some data with it so if there are no subscribers if the
event type doesn't exist in
dictionary subscribers then we don't have to do anything otherwise we're going to
go over the entire list of subscriber functions and call each
of those functions with the data so there you have it this is actually a complete
well almost complete event system and this actually works just fine so now what we can do
we can go back to for example the user functions like registering new user and then replace all
this stuff with simply a posting an event goal and as the data i'm going to pass
along the user and same thing here then of course i also need to up update my imports
and now i can remove parts of the imports that are no longer needed here and i forgot to
add the user there so much shorter this way and now what i can do is that i can kind
of group these different event handlers together in the way that makes sense so for
example i could add here a slack event handler so create a file slack listener
then what i'm going to do is import this post slack message that
i'm ma
inly going to use to do that and of course i need the event library and then first thing is let's add a function that handles a user registered event and there
we're going to put the slack message to sales and of course don't forget to spam this
person there you go so there we have our handle user registered event now i've got
an import here and i'm also going to add a function that sets up the event
handling system for slack messages and it's going to call the subscribe method
that subscri
bes us to a particular type of event and then user registered event should be
mapped to the handle user register event function and now i can add more functions here
like when the user upgrades the plan etc etc now in a similar way i can do that for for example
the system logs so add the log listener file i'll just copy this over because i
need to do a lot of things the same and now i'm not posting a slack message
but i'm sending a system log event and i can do the same let's say i want
to
have a password forgotten event like this and they add a
subscribe method goal here so i created these two collections of
listeners and in the observer i then should import those and call this function so
that the event system is set up correctly so i'm not going to show how this works for
all the functions and all the methods in this particular example but i made a kind of complete
version of this example where i rewrote this api so that it now uses this event system that's also
in the s
ame repository so if you check it out on github you can look at it and play with it
yourself so you see we have here this event system that i just added and we have a log listener with
a couple of different event handlers and we have slack listener that looks the same and we have
an email listener that actually sends the emails and the user and plan api access points now simply
post an event instead of using directly sending emails posting logs or posting slack messages and
something really
interesting happened here if you look at the way that it's now structured and the
way that the imports are organized is that here you see that the only dependency is on the event
system there's no longer any dependency on the type of specific type of message or specific
type of things you want to do depending on the event that occurs and the same is for the plan
so there is there's just the database stuff and the event stuff that's it and the methods are also
becoming much shorter so we ju
st have the database operation and then we post the event if you look
at the listeners themselves you also see that the number of imports is actually pretty low
it just imports event stuff that it needs and since this is a slack listener it
needs the post slack message import and the same for the log and for the email so
using the event system in this way has allowed us to really separate the code much better if you
look at what we do in the actual test program so we're simply initializing
the event structure
by calling up these event handler setup functions and then when i run this you see that it generates
a bunch of emails slack messages log messages etc when i call these functions here and now it's
really nice that not only are the imports much better structure that has less coupling because of
that some things are now much easier to change so for example if i want to temporarily disable slack
messaging i could simply put this into comments and if i run the code again the
re are no more
slack messages i didn't have to change anything in my actual api functions because these simply
post an event and don't know anything about what happens after that now similarly if let's say i
wanted to change from slack to a different message provider like microsoft teams or whatever you use
in your company then also that would be a pretty easy change i would simply disable this and then
add a new listener for my new messaging service and enable event handling and i'm basica
lly done i
don't need to change anything else in the code in the previous version i would have to sift through
each of the functions in my back-end servers and change the slack specific part to a teams specific
part so there you see a very clear example of how reducing coupling helps you write code that's much
easier to change later on and this event mechanism i said before it's really really useful for these
kind of things and a lot of people only use events for ui stuff like handling a bu
tton click or
stuff like that but just keep in mind that if you encounter this kind of structure or this kind
of thing in your code that events can really help to clean it up you know on this channel i like
to talk a lot about writing code and designing software i don't like to just provide recipes like
how to do x but i'd like to think a bit more about why we're doing things the way that we do
them i've been teaching computer science for over 20 years and i've started several
software bus
inesses and simply following recipes never really led to good results for me i
really needed the understanding that's behind it on the other hand there are lots of tutorials out
there about design patterns and design principles but often the examples are not very practical
what i discussed today was a solution to a problem that i literally had a few weeks ago i hope this
video helps you think differently about how you write code let me know in the comments below what
your thoughts are about
this you can find a link to github repository containing the code that i
worked on in this video in description below if you're enjoying this series consider subscribing
thanks for watching take care and see you soon you
Comments
Event systems are a good example of a design that results in code with low coupling and strong cohesion. This video delves into coupling and cohesion in more detail: https://youtu.be/eiDyK_ofPPM.
I love the sense of humour all over the place. Your tutorials are the first ones that actually managed to get me interested in design patterns. Please never stop making videos!
Arjan: "I'm going to show you how to use the Observer pattern…" Me: <watches>
These kinds of tutorials are incredibly refreshing! Thanks, Arjan! Most programming tutorials are about either learning a language, a framework or building some kind of bogus app. These topics are usefull of course, but at a point they will either be too basic or too specific. Your video's are both advanced/expert level AND generic enough to be interesting/educational for a lot of programmers. Kudo's, vriend!
Clear and useful explanation! Using an observer can be extended to abstract over the event transport. Then you can do things like retry failures or even plug in a distributed pub/sub system. Constructive feedback: I would like if you spent a little time to also cover limitations/drawbacks of the pattern you're discussing in each video, and the circumstances when it would be inappropriate or overkill. Design patterns are tools for architecting software, and it's important to know when each tool is the best for the job, and also when one should look for a different tool.
Arjan if you keep up these videos, this channel is going to explode. As an amateur python user who has gone through some Kaggle courses, freeCodeCamp courses, and primarily written code as a hobby and as a necessity when needs have arisen, these videos are phenomenal and engaging content. I took the time to like and comment, and am subscribing and look forward to future content!
Never commented on a Programming video before, the way u presented the whole idea made me to comment on. Kudos, and keep up the good work.
Just want to complement the production quality of this tutorial, both the editing and content are great!
This is insane quality content, keep it up !
Great video! Keep up the great work. Looking forward to watching more.
I would not have decoupled logging in that way. That feels integral to what the function is supposed to do. And I like logs to be very obviously and simply related to code that's doing the logging. But yes, this pattern is powerful and useful, and the example you chose was (aside from the logging bit) compelling.
This is my favorite pattern. Event-based programming works amazingly well with micro services and allows you to super easily scale asymmetrically i.e. the ML portion of your architecture vs the I/O portion.
Great overview! There are tons of use-cases for this, I'm glad to have now seen a concrete example of how to use it. And fun fact, this would have helped me in an interview with Amazon about a year ago!
Your attention to details, all the way down to the volume of the background music, is immaculate.
Thank you. I'm looking forward to the rest of the series. This type of content is rare, much needed and much appreciated!
I love that you showed the UML and explained the concept really well before hand. Event handlers are used in so many places but it is not always clear what is going on when you look at existing usages.
I really appreciate that you built the event system to show its internal workings instead of using an existing library, thanks!
These videos provide so much help for me! As you said, there are a lot of tutorials out there on design pattern recepies but no one really explains when it might be a good idea to use them and their advantages when it comes to get a better cohesion and decoupling your code. This channel is a real treasure trove! Cheers from Sweden
This channel became my favorite python channel. Thanks for your work, I'm learning a lot!!!!
Is it a coincidence that I started reading the same book about Design Patterns and I suddenly come across your channel? Brilliant.