Demystifying Design Patterns: A Practical Guide for Developers

Do you ever worry about your code becoming spaghetti-like and difficult to maintain? Master the art of crafting clean, maintainable, and adaptable software by harnessing the power of design patterns. This presentation will empower you with a clear, structured understanding of these reusable solutions to address common programming challenges.

We'll delve into design patterns’ key categories: Behavioral, Structural, and Creational, as well as explore their functionality and how they can be applied in your daily development workflow. For each category, we'll also explore a practical design pattern in detail and showcase real-world applications of these patterns, along with small-scale code examples that illustrate their practical implementation.

You'll gain valuable insight into how these patterns can translate into real-world development scenarios, such as facilitating communication between objects (Behavioral), separating interfaces from implementation for flexibility (Structural), and enabling dynamic algorithm selection at runtime (Creational).

This session took place in track Programming & Software Engineering.

Transcript (auto)

Auto-generated from the recording utilizing Open-Source AI. Speaker labels (Speaker 1, Speaker 2) reflect diarization, not identity. Timestamps refer to the recording.

Speaker 1 [00:07]

Thank you everyone. First of all, I love Germany. This is my first time in here. I work for Bloomberg, traveling from New York office. So really appreciate it. Thank you for joining today. So I'll start with a quick question. What comes to your mind if you think about a spaghetti code? Just think about it. Anything? OK, so what exactly is this spaghetti code? So it's code, a class. Have you ever had any class which had so many functions that you can't recognize what exactly is this thing doing? And while naming the class, you just forget about it. Why is it named like that? It has so many functions, I can't even recognize what this class is actually doing. So does this sound familiar to anyone? Fierce, like, just give me a, just checking the room. What causes spaghetti code? That's the main thing. So it could be a lack of planning and design. It could be a tight deadline. We have all come across it. Or adding a feature without refactoring. But even if you plan everything, you cannot do everything on time or ahead of the time, right? Does everyone, like, are you following with me? Just give me a nod. Okay, good. Quick brief about me. I'm Tanu. I work as a software engineer at Bloomberg, New York office. Really, really excited about this talk. This whole concept of design pattern has helped me become a good programmer. And I've been using it all the time. I've been using it. So, in this presentation, the knowledge that I'm planning to provide you is going to help you avoid poorly structured code. So I'll just quickly move to the agenda. So we'll first go through the basic pillars, which will cover the unified modeling language in which we can visualize the whole code base. And then I'll talk about solid principles. These are the topics which I expect everyone to know, but it's fine, we'll just brief touch upon it, and then we'll move on to the categories of the design patterns. My target, just to give you a brief, my target in here is not to teach you what design patterns are. What I'm trying to explain to you is, whenever you're making a high-level decision in your code base, what I want you to think about is, why are we using this design pattern? a fundamental concept behind any of it before even implementing it. So keeping that in mind, let's dive into the main concepts. Starting with the basic pillars, unified modeling language, that means UML diagrams. So it's a narrative told through shapes and lines. So for example, we have a parent class, a child class, another child class, basic concept, Inheriting. Inheritance. So, UML diagram basically is visual communication tool for the software system, right? So, how does inheritance look like in UML diagram? So, deriving a new class again from an existing one. You have, let's say, in our example, you have a payment interface and you create the implementation classes out of it, which could be a cash payment, card payment. so basically what we are doing in inheritance what this diagram shows is you are inheriting the properties and sharing the behavior so it is a relationship all these things will help us understand the design patterns later on so it is a relationship with a dotted line and a triangle and cash is a payment card is a payment sweet so So, and how does aggregation looks like? One object referencing other object, a diamond, a diamond, it's not and is a relationship, I'm not inheriting anything, I'm just referring an object. That means those objects, objects exist and I just want to use it whenever I need it. For example, in this case, Playlist has a list of songs. And again, as I mentioned, it's a diamond. And again, Playlist is referring to the songs. It's not creating. It's just referencing it. So UML diagrams, so we understood what UML diagrams are and how the concept is used. This will help us understand how to represent design patterns and how your system design looks like. So moving on to that next basic pillar, which is solid principles. Just to quickly read the room, has anyone heard of solid principles or ever used it? Just give me a. OK, OK, OK, nice. So solid principles are basically a set of guidelines for software development. They'll help you move away from unstructured code to a well-structured, adaptable code. I know it's a lot of jargon, but bear with me. So these are not strict rules. These are principles and guidelines which will help you write better code and flexible softwares. So moving on to what exactly it means. This is the full form, but this doesn't help us. This is a solid principle. In this presentation, I'll only cover three of them, not all of them, for the sake of this presentation. So let's dive into each one of them, starting with single responsibility. Single responsibility principle explains us that a class should focus on one and only one aspect of the system. Means a class should have only one job, not more than that. For example, what does it mean? Let's say we have an order class, which is processing the order and sending the confirmation. Pretty straightforward, very basic system, right? But this class is doing more than one job. It's processing the order and also sending the confirmation. But when a class has more than one responsibilities, it becomes hard to manage it and modify it. So we break into two classes based upon the kind of work it's doing, the business logic. First one would be it's a separate notification manager which sends the confirmation. And the second one would be breaking the order to just process the order. So basically, two classes doing solely one responsibility. That's single responsibility principle. Moving on to the next principle, open-close principle. So open-close principle explains that a class should be open for extension, but closed for modification. What does it mean? Let's take an example. So we have a notification manager class here, which is sending the email notification. And let's say your product comes like, OK, now we want to support SMS notification as well. from now on. As a general, given a tight deadline, what I'll do is just pick this class, add another method, and just write another send notification method. But that's what the open-close principle talks about. The open-close principle talks about that you should not modify an existing class. Class should be closed for modification. So the solution is you abstract the functionality, you create those particular implementation classes, email notification, SMS notification, and then extend it. So you're sharing the behavior. Moving on to the last principle, I'll skip this cost principle and interface segregation. Going on to the last one, which is dependency inversion. It says that class should not depend upon the concrete implementation of another class. They both should depend upon the abstractions. For example, user repository is dependent upon dbconnection. It's calling it, getting the user data. But that's what the catch is. It should not depend upon the concrete implementation. It should depend upon the abstraction of the other class. Why? Because if dbconnection changes, the changes would ripple to the client. Basically, the user repository would change. So we would want to avoid it, making it more flexible and less rigid code. That's why. And this is the reason why we need the abstraction in here. So user repository is a higher level class. DB connection is a lower level class, basically client and an object database connection. Separate them, connect them by abstraction to make it more flexible. So wrapping up, since we didn't cover those two, solid principles, single responsibility, Just do one job. Open-close principle, don't modify an existing class. And dependency inversion, make them connect via abstraction. Don't make them directly linked. So moving on to the main topic of this presentation, which is, what are design patterns? So any thoughts? Has anyone heard of design patterns? Just reading the room. OK. Nice. Design patterns are pre-packed solutions for typical software design problems. So, but how do I define a problem? Like, to use a design pattern, I need to know what the problem is, right? And without understanding the underlying fundamental problem, I can't use any random design pattern, right? So, what my target in this presentation is not to teach you design patterns. My target is to explain you why do we even have these design patterns? Like, what fundamental problem they are trying to solve? So, for that, we need to understand the categories of the design patterns. So they can be categorized into three categories, which is creational, structural, behavior. Creational patterns basically talk about object creation mechanism, how objects are created. It hides the actual instance, like the nasty business of creating the objects and moving Moving on, all these are German brands, just for the sake of it. And then moving on to the structural pattern, you are making existing objects compatible with each other. So I don't know how to speak, I don't know German. So if I want to talk, I'll have a translator or a friend who could understand that. So structural pattern is basically making two different interfaces or different objects talking in a different language, compatible with each other. And the last one is behavioral design patterns. For example, you don't need a travel agency to travel from Munich to Frankfurt, but let's say you want to travel from Munich to Frankfurt using a travel agency. Based upon your criteria, how much time you have, how much money you have, they can suggest to you different kinds of mode of commute, right? For example, you can go by car, flight, or even walk. So in here, you should observe that the behavioral patterns are not responsible for creating an object or making anything compatible. They're just using existing objects and delegating the responsibility. So if I tell the travel agency, I have this, this, this requirement, they'll come up with a solution, existing solution. They are not creating anything by themselves. So behavioral patterns are basically linking other objects, orchestrating. So to give you a big picture, we'll talk about different types of patterns and what that pattern looks, how that pattern looks like, and why do we even have that. So creation pattern deals with creation of the objects, creation mechanism, structural, making objects compatible, and behavioral, orchestrating between objects. so let's pick we will stick with the solid principles to actually create those design patterns, like what problems are we solving and that creates a big design pattern in itself so starting with the creation pattern factory pattern, most fundamental one, let's take an example here a client is trying to create an object, card and cash basic check if it is card, create a card object if it is cash, create a cash object, right very simple But let's say with another client requirement, you have to add cryptocurrency. You create another class based upon requirement. So what we did, we modified the client, right? Which breaks one of our solid principles, which is open-close principle. We modified an existing class, right? So how do we fix this? It's very, very generic. I have done this thing multiple times. So in order to fix this, let's just keep our client, first of all, aside for a minute. Let's say the client wants to just make a payment, wants to create, make a payment, wants to create those objects based upon the checks. So first of all, let's fix our dependency inversion. If you observe, the client was directly creating those objects, which should not be allowed. Client should depend upon the abstraction. So let's first fix our dependency inversion problem, which will be done by creating an abstract class, and then asking all these classes to implement that specific behavior of the payment class, right? But how does it help? I still have to create those objects based upon the checks. So in order for us to help client, making it agnostic of any changes, will delegate that change to the if-else-nasty-fills conditions to a factory, payment factory. Payment factory will take care of that if-else conditions, and based upon those checks, it will give the object to the client. Just tell me what you want. You don't need to take care of creation mechanism. So payment factory uses the abstraction, creates those objects, and client will use the payment factory eventually. So it looks like, how does it look like in the code? Previously, without factory button, the client was creating card object, cash object, and cryptocurrency object, because we modified also. But now with factory button, the whole nasty business of if else condition is taken care by payment factory. What client needs to do is just have the reference to the payment factory, and it will just provide it what type of object it wants. And the payment factory will, in return, give the object. So essentially, we delegated the nasty first condition to a payment factory, to a factory class. So overall, factory pattern in the category of creation pattern, the fundamental business that we're doing is creating object. So what did we learn? We are instantiating the object, and we abstracted the creation process from client to the factory. That's the underlying fundamental, most fundamental behavior, following the solid principles. So I'll move on to the next pattern, which is structural pattern. And one of those patterns in this category is adaptive pattern. Taking an example again, we have a client who wants to make a payment, who wants to execute the payment. Just observe, I'm not creating an object, who just wants to execute the payment. Both of them exist. But let's say because of some third party library integration or anything, your payment processor now expects you to transfer JSON and not XML. So client is forced now to change or do the pre-processing or the post-processing based upon the third party or the downstream service, right? which is a problem. We will be forced to change the client. Again, we are modifying the class. Again, breaching our open-close principle. So, and also if you observe, just to give this example, client is directly calling the payment processor, which should not be allowed. You're directly calling the concrete class. So let's fix those problems in this scenario. We'll keep our client aside for a moment. Keep our party payment aside for a moment. Let's first fix the dependency inversion by abstracting. So dependency inversion, as I told you, will not make them depend upon each other directly. So we have this class, execute payment. But this doesn't help because we still need to do the processing, right? XML to JSON. So we create an adapter. The adapter in between will take care of the pre-processing, post-processing. That means XML to JSON or vice versa. The client will then, so it does the pre-processing, parsing the data, and will make the call to the processor. And in return, client will call the payment service, use the payment service. So essentially, I decoupled, if you observe, I decoupled client from the processor or the third-party application through an adapter, which is taking care of the nasty business of parsing and everything. Without adapter pattern, this is how it used to look like. client was directly calling the payment processor with adapter without with adapter pattern. Now client has to just use the adapter. Adapter will do the nasty business of processing, parsing, and then eventually calling the third party payment processor. So, wrapping up this category, what did we learn? We made two incompatible interfaces, compatible with each other. Two incompatible objects compatible with each other. We didn't create anything. That's what the fundamental difference is. Structural patterns. So, moving on to the last one, considering the time I left. Behavioral pattern, strategy pattern. Very, very important. This whole category, you won't even realize how many times you use it, but you may get easily confused. I'll quickly take another example. A client trying to call card handler to make a payment, tap payment, or a cash payment. We are not creating an object. We are just using the objects again. It's different from the creational patterns, in which factory patterns, we were creating the objects. We are here. We are just calling it to make the payment. Objects exist. Now, let's say another type of payment you want to support, cryptocurrency. What will we do? Modify a client, right? Create another if-else condition, checks, execute the payment. WHICH IS WRONG, WE AGAIN BREACHED OUR OPEN-CLOSE PRINCIPLE, WE MODIFIED AN EXISTING CLASS. SO LET'S FIX IT, KEEP OUR CLIENT ASIDE, AND LET'S FIRST MAKE THIS THING COMPATIBLE WITH THE DEPENDENCY INVERSION PRINCIPLE. SO I CREATE AN INTERFACE, MAKE THESE CLASSES COMPATIBLE WITH THAT, AND THIS IS HOW WE FIRST FIX THE DEPENDENCY INVERSION. them abstract don't don't depend upon the concretions and the client still if you have another kind of payment the client will still have to have an if else condition right new payment system client will have to modify so we need another intermediate class which will take care of the payment processing like what type of payment is there and then make a call here we are not creating we are making a call, which is processing. So client here, checkout service is referencing those objects. Checkout service is not creating. Checkout service is not making anything compatible. It's just using existing payment objects and then just making the call. Client will eventually call the checkout service, tell it card, cash, crypto, and then delegate it to the checkout service. And eventually, it will use the payment processor, the underlying classes to make the processing of the payment. So with the strategy pattern, we have the card payment. Checkout service will have all the underlying logic. And the client just needs to call it. And the client can reuse the existing checkout service, tell it what type of payment is there, make the processing. And then the rest of the work will be taken care by the checkout service. So what did we learn in the strategy pattern is object responsibility distribution. Think of it as an orchestrator, music orchestrator. You are just delegating the musicians what to do. You're not doing anything, you're not creating, you're just making the symphony. Do what you want to do. What and when to do. That's what behavioral patterns are. This is the last slide. More patterns in each category. creational patterns, factory pattern, builder pattern, singleton, and structural again, making some things compatible. Behavioral pattern again, orchestrating how you want to orchestrate, when to call which method based upon the timing and that's what it takes care of. So to conclude, I'll just take one more minute. Single responsibility from solid principles. We started with UML, we started with UML, understanding how to represent a system. Then we moved on to solid principles, learning what are the fundamental concepts of designing. And then we moved on to the categories of design patterns, why and what problem these design patterns are actually solving. And so yeah, that's all. And thanks for attending. And thanks. I really appreciate the time and patience. If you have any questions, please feel free. Shoot it on me. So thank you.

Speaker 2 [23:49]

Thank you very much with this we'll dive into the questions So the first one is Python is a more function based language compared to Java or C++ where classes are the main structures How to deal with design patterns in Python?

Speaker 1 [24:01]

Yeah, that's a good question. So, basically, you need not see, as long as a language is dealing with object-oriented programming algorithms or design principles, you can easily use all of that in Python. Java has methods, but you can think of Python as functions. So, if you want to inject anything, I had one slide which was just talking about this thing in which if you see function as a behavior, function or a method is a behavior if you understand that aspect you won't see any difference so if you want to inject a behavior or you want to pass a behavior like lambdas or passing the whole category of behavior pattern is just behavioral based so functions or methods as long as Python is object oriented based language all of these patterns you can easily easily use them so yeah

Speaker 2 [24:57]

Thank you. The next question is, why should I prefer abstractions and inheritance over dependency injection? Aren't abstract classes a source of spaghetti code?

Speaker 1 [25:08]

That's a good question. Sometimes it happens that trying to over-optimize stuff which doesn't need optimization can also lead to spaghetti good. But what I'm trying to explain here is not to choose between one thing or the other. What my target in here is to educate that, going back to my first slide, was also we don't have all the requirements before hand. And given the timelines, deadlines, we cannot figure out everything beforehand, right? So it's not possible. I do agree. Doing too much of abstraction, creating multiple classes could take you to another further downhill. But again, it's a source. But if you know in future, you may get a lot of requirements and how dynamic your project or product is and how much time you're given, based on that, just choose one thing. Otherwise, it's totally fine. If your code is working, go for it.

Speaker 2 [26:05]

Thank you and maybe we have time for one more question. What are your thoughts on abstraction using ABC versus protocols in Python?

Speaker 1 [26:12]

I'm actually not sure of the first one.

Speaker 2 [26:16]

Sorry, I'm not

Speaker 1 [26:16]

I'm not sure about the first part.

Speaker 2 [26:18]

So what are the thoughts on abstractions using ABC versus protocols in Python?

Speaker 1 [26:23]

I'll have to think about it, so maybe whoever has asked, let's talk about it offline.

Speaker 2 [26:30]

Okay, then maybe I can ask you the next one. Do you know a good UML tool or editor that can generate Python class stubs?

Speaker 1 [26:37]

Yeah, yeah, yeah. There are many. So there is one, Miro. Miro is one, but it's a paid tool. Maybe they have one month of free subscription. And there is UML Editor also, which is a free tool through which you can just download those diagrams and just play around. It's just a playground. So I think that should be it.

Speaker 2 [26:59]

Thank you, and with this our time is over. Let's kindly thank the speaker again

Tanu

Tanu is a Software Engineer at Bloomberg on the BQL (Bloomberg Query Language) team. BQL provides intelligent query suggestions to empower users for efficient data exploration. A passion for crafting clean, maintainable, and efficient software solutions fuels her work in this role and throughout her career. She has a Master's degree in Distributed Systems and 6 years of industry experience building scalable systems. She is a tech writer for Medium, has organized hands-on workshops and delivered technical presentations internally for 100+ people . She is passionate about staying on top of tech and sharing knowledge at conferences. In her free time, Tanu enjoys traveling and playing music.

Social card for talk: Demystifying Design Patterns: A Practical Guide for Developers