This refactoring masterfully applies the Open/Closed Principle to eliminate brittle conditional logic. It transforms a rigid service into a scalable architecture where adding new features no longer requires modifying existing code.
Deep Dive
Voraussetzung
- Keine Daten verfügbar.
Nächste Schritte
- Keine Daten verfügbar.
Deep Dive
Replacing Switch Statements with the Factory Pattern in C#Hinzugefügt:
In this video, we are going to explore one of the top redesign patterns in software engineering, the factory pattern. I'll walk you through a practical example and explain why you should know about the factory pattern, what problems it solves, and what benefits it brings to your codebase, and of course, how you can implement it. So, let's dive in. I'll use this notification service class for this demo. And let me quickly walk you through what's going on inside. The notification service is responsible for sending out a notification to a specific channel. And these channels can be an email, an SMS message, a Slack message, and of course, we can expect that we may want to extend this in the future with more messaging channels. Now, what's also interesting about the current notification service implementation is that it's responsible for creating the specific sender depending on the notification channel that we want to use. So you can see it's either creating an SMTP client and a mail message or initializing a Twilio client for sending an SMS message or creating a Slack API client and it's also pulling configuration values to configure all of these. So you can see it has a lot of responsibilities. Now on top of that it's also duplicating the code inside of the send bulk async method that just allows us to send multiple notifications through a single method. And the code inside is a one for one copy of what we have inside of send async. So in its current form, the notification service has a couple of problems. First, it needs to know about how to construct the dependencies that it uses for sending our notifications. Also, it's kind of violating the single responsibility principle. It's both responsible for constructing these services in their correct state as well as sending the actual notifications through each of these channels. In order to extend the behavior of the notification service, we have to write more code. For example, if we wanted to add a new channel in the future. And last but not least, the current implementation is difficult to test because we can't easily mock these dependencies as they are constructed in the code. They're not injected through DI through some abstraction. So, we don't have an easy way to mock these if we want to test them out in isolation.
So, we want to use the factory pattern to solve this and I'm going to show you how step by step. But first, let's start by slightly cleaning up the implementation of the notification service. For example, we can solve the code duplication in the send bulk async method by simply falling back to the existing send async method. Since the behavior inside is a one for one copy, we can just call the method for sending out a single notification and pass in the respective parameters. So let me just line up these arguments vertically so that everything fits nicely on the screen and then we can get rid of all of this as it's already contained inside of send async. And then the second improvement we could make without yet introducing the factory pattern is solving the problem of using a string channel. This is usually called primitive obsession and it's problematic because it's kind of easy to introduce typos in here that are not so simple to catch if you don't have proper testing.
So what we can do is use an enum for this and it's a perfect solution for what I like to call magic strings and we can call this type the notification channel and I'm going to make this a public enum and then let's say our channels are email, SMS and Slack. If we want to add any more channels in the future, we're just going to extend this enum. Now, how does this change our send async method? Well, here we're going to accept a notification channel enum value and then our comparison becomes strongly type as we can compare to the specific channel. So, in this case, we've got email, then we've got SMS, and finally we've got Slack. And our code is starting to slowly look a lot more readable. Now let me also fix the notification request. So I'll replace the channel with a notification channel.
We no longer need a default value here.
And now our current implementation will compile. Now I'm going to make a copy of this type. And in the copied class I'm going to rename it to notification service before. And I'm going to also rename the file to match our type name.
And I'm going to keep this in place so that we can compare it with the final result. But I'm just going to keep the couple of improvements here for using an enum and just getting rid of code duplication. So let's close this down and we'll go back to our notification service. And to fix any compile errors, I'm going to rename this request here to be just a notification request. I can get rid of this comment here. And I'll also get rid of this comment because it's not relevant for this version of the notification service. So then what do we want to do next? We want to extract the logic for each of the channels into a class that will be responsible for constructing the object required to send this notification and it will also be responsible for sending out the notification. Now we have to start from an abstraction and I'll create a new folder here. I'm just going to call this the factory and inside of this I want to create a new interface and I will call this the I notification sender. So this needs to contain the logic or a sender for a particular channel. We can also have a property representing which channel this is and we need a way to encapsulate the actual sending of the notification. So we can have some sort of send async method but we do need to extract the common arguments and these are going to be the recipient, the subject and the notification body. Of course we don't have to use primitive types here. We can nicely wrap this into a record. Let's call it a notification message. and it can contain the same arguments. So we have a recipient, a subject and a body.
And I can now simplify the send async method to just accept a notification message. And now we can extract the creation logic for the SMTP client into its own notification sender. So let me create a new class here and I will call this the email notification sender.
Let's make this an internal seal type.
We're going to implement the I notification sender interface and we're going to make the channel a readonly property returning the enum value directly. Then we're going to take the logic that I have here and let's drop it into the send async method. Now we're obviously missing a couple of dependencies. We'll have to fix a couple of naming conflicts. So this has to be a mail message. We also need a way to extract the configuration values.
Although there's a better way to solve this and we need to pass in the respective values. So I'll pass in the recipient, the subject and the body from the notification message. And then what about the configuration values? Well, the recommended solution for this is using a strongly typed class like a settings object and then injecting this through I options. So here I can say I options of SMTP settings and then we can set this as a value of the field by saying SMTP settings and then we take the value of the I options instance now and then finally I should be able to replace this non-compiling code with the proper references from our SMTP settings class. So we need to pass in the username also the password and then finally the default from address and our sender is looking a lot cleaner and we have a simple way to pass in the SMTP settings for something like environment variables or even an app settings JSON file which is typical for ASP.NET core APIs and also we've nicely encapsulated the creation logic for setting up an SMTP client inside of our notification sender. The next thing we need to do is to repeat this for the SMS channel and the Slack channel. And I'm going to drop this code in in order to save us some time. And they're going to look something like this. So nothing too spectacular. Just implementing our I notification sender and extracting the logic that was already inside of the notification service. So we're basically moving code around, but we're getting close to our final design. Now we need an abstraction that's going to tie everything together. And I'm going to call this the I notification sender factory. So this is what's actually going to be responsible for initializing an instance of a notification sender.
And the only argument it needs is which channel to use. So with this in place, we can now refactor the notification service to only depend on the I notification sender factory. which means our notification service no longer needs to know about I configuration or how to construct any of these. So I'm just going to delete all of this code and all we want to do inside is use the factory to create a new sender. So we're going to say factory create sender pass in the notification channel and then we're going to say await sender send async initialize a notification message and we just pass in the recipient subject and the body and we're done. So we reduce the code in the notification service to just calling out to the specific implementation of the I notification sender interface and it's also not responsible for creating this object as we delegate this to the notification sender factory. So then we need to implement this type. So let's add that here. I'll call this the notification sender factory and it's going to implement our interface with the same name. So we have a couple of options here. I'll show you a simpler one which is simply having an in-memory dictionary where the key is the notification channel and then we resolve an I notification sender and we initialize this through the constructor by injecting an innumerable of I notification senders and we're going to turn these into a dictionary. So we'll say senders and select those with two dictionary and for each sender we'll use the channel as the key and the sender instance itself as the value. So now in the create sender method, we'll say senders try get value. We'll pass in the channel and try to get back a sender instance. And if this fails, we're going to throw an exception. Otherwise, we are going to return the sender that we found. And if you watch my recent video about the strategy pattern, it's very similar to this. In fact, it's almost the identical structure. And even though the implementations are similar, the intent between these patterns is a bit different. The factory pattern is creational. It solves the problem of how do we construct some complex dependency so that we don't have to care about how to initialize this object in order to use the functionality it provides. So the factory pattern is a creational pattern and on the other hand the strategy pattern is a behavioral pattern and it's more concerned with encapsulating the business logic for a specific problem that we are trying to solve. Now a couple of remarks about some implications of the current design.
If the notification senders can be for example singleton services that we typically only create once then this design could be fine as it's going to have minimal performance impact. We would create the senders once and a notification factory once and we wouldn't have to pay the price for this on every request. However, if we need to create this or request, then the calculation becomes a bit different as injecting all of the senders as a dependency means they will all be constructed. And we usually only use one of the senders. We're wasting resources initializing the unnecessary ones. So, I'll make a copy of this just for reference. And let's call this the key notification sender factory where instead of depending on the senders we could inject an I service provider. And then we can use the service provider to dynamically resolve the correct dependency. So let me actually move this into a readonly field. And this is just going to be a temporary step to a primary constructor. And then how does this change our create sender method? So I call this the key notification sender factory because the idea is to use a key service to resolve our dependency. So then inside of the create sender we can say something like service provider get required key service we'll specify I notification sender as the type we want to resolve. Now I will have to add a using statement here. So I'll say using Microsoft extensions dependency injection. And now this should compile and I can pass in the notification channel to get back my key service provided it's registered as a key service. So this simplifies the factory even more. Finally, let's wire this up with dependency injection to show you what that's going to look like. And I'll drop in the code. So we're configuring our settings classes and binding them from let's say app settings or environment variables. We're setting up our notification centers as singletons.
We're also adding our factory and our notification service. Now, what would change if you wanted to go down the key services approach? Let me make a copy of this. So, we still keep our I notification sender factory as singleton, but we use the key implementation. And I'm going to replace add singleton with add key singleton.
So, let's do this. In the remaining calls, we'll need to provide our service key. And for this, we can use our notification channel directly. So I can say notification channel email, notification channel SMS, and then notification channel Slack. And this will work with the native dependency injection support. Finally, what's the payoff of making all of these changes?
Well, now if we want to extend our notification service to add support for another notification channel, we have to make changes in two places. We extend the notification channel enum to add a new notification channel. Let's say we want to support Microsoft Teams notifications. Then we add an implementation of the respective I notification sender for our new channel.
We said we want to support Microsoft Teams. So the implementation could look something like this. Then we wire this up with dependency injection. So let's bind the settings value. So I'll say team settings. We'll pull this from the respect section. We'll add this as a singleton service. So let's say teams notification sender. Or we can also use the key services approach depending on which one we prefer. And let me just give you a demo of what this looks like.
I'm going to comment this out for a moment. And I'll drop in some code below to send some notifications to different channel types. And if we execute this, we should successfully resolve each of the notification senders and send out the notifications to their desired destinations. So just a reminder that we went from something like this which had about 100 lines of code a lot of complicated boilerplate logic for initializing the notification senders for each of the channels. Now we did slightly improve it with adding an enome and getting rid of duplication. But with the factory pattern we can abstract this into an I notification sender interface implement the respective sender instance where we can nicely encapsulate the creation logic and then we delegate the construction of the specific notification sender to a factory. We can use a dictionary here or key services or a solution more oriented toward modern dependency injection. Although this here is basically a service locator. And finally, we can update our original service to just a couple of lines of code where it uses the factory to initialize the notification sender and then send out a call to the specific implementation instance. If you want to grab the source code for this video, you can do so from the pinned comment right below. And if you want to see how this compares to the strategy pattern and why people confuse them all the time, then I recommend taking a look at this video next. If you enjoy this video, gently tap the like button below to let me know.
Ähnliche Videos
Agentforce NOW AMA: Build with React and Salesforce Multi-Framework
SalesforceDevs
490 views•2026-05-28
How agent o11y differs from traditional o11y — Phil Hetzel, Braintrust
aiDotEngineer
450 views•2026-05-28
Re: 🗣️📍theprophedu📍2026 GST 103 CLASS (E-EXAM REVISION)
theprophedu
636 views•2026-06-04
WEB TECHNOLOGIES UNIT-2 | Degree 4th sem BCOM Computers web technologies unit-2 full explanation💯✅
LearnwithSahera
1K views•2026-05-29
More tests are always better? How to use AI to identify tests that bring little value
Alliance4Qualification
335 views•2026-05-29
Search Algorithms Explained in 60 Seconds! 🤖💨
samarthtuliofficial
218 views•2026-06-01
People of Game of Thrones using JavaScript DOM
AltCampus
296 views•2026-05-30
Introduction to Problem Solving Part - 1 | Lecture 1 | Intermediate DSA
ascensionix
107 views•2026-05-29











