You HATE This Design Pattern But It Enables Refactoring!
January 1, 2024
• 1,147 views
In this video, I am going to show you how the design pattern that we all know and dislike can help us incrementally refactor usage of global static variables. I'll explain why this kind of usage is unsafe, why this pattern seems to work really well, and how we can move to dependency injection... All by taking small steps!
Have you subscribed to my weekly newsletter yet? A 5-minute read every weekend, right to your inbox, so you can start your weekend learning off strong:
https://subscribe.devle...
View Transcript
so the Singleton is a design pattern and no one likes it but it's going to be the thing that helps us out here in this video we're going to look at a refactoring technique to remove Global constants and this is something that I've used in a professional setting that seemed to work really well we'll be starting with some usage of variables on a static class that are publicly available and then moving towards a Singleton pattern and then from there being able to refactor towards dependency injection we'll walk through this all in code so you can see how it's done a quick reminder to check that pin comment for a link to my free weekly software engineering newsletter and before I dive into the code I just wanted to talk through some Concepts first and then hopefully the technique and the code we're looking at will
make a little bit more sense so when I talk about globally accessible variables and having these on static classes so that anyone in a program can access them I want to talk about why this is a little bit dangerous the first part is more philosophical and it's that the different parts of your system there's no real control or guidance over who's able to see what there's no enforcement of responsibilities and because it's something that's Global everyone can see it and on the surface that's not necessarily such a bad thing it's just that it's a little bit weird to not be able to have uh constraints or control when you're implementing parts of your system and if things were just read only on those globally accessible variables really that's not so bad but it gets a little bit more dangerous when we start talking about mutability
when we're talking about static variables these are shared State it's one particular piece of state that we have for that varable for the context of your entire application so if it's not constant or it's not able to be fixed in place and it can change this means that you have different parts of your application that can be reading this information and at different points in time it could be changing without them totally knowing or having control over why that's happening there might be situations where if you have Global accessible state that that state could be changing behind the scenes due to something external or you might have different parts of your application that could be mutating that State and if you're doing things out of order that's where things can get pretty weird because there's no real control or enforcement and if anyone in the
entire application can touch and modify the state of that variable all of the other parts that are consuming it are at risk of having that changed under their feet so consider how complex this can get if you have asynchronous code that's running or consider if you have hundreds or thousands of tests in your test suite and you have something like this executing out of all of a sudden your tests start breaking and you run them again and they start passing and the flakiness and having to debug tests like that is an absolute nightmare so what about the Singleton how is that going to help us here so the Singleton is a design pattern and no one likes it but it's going to be the thing that helps us out here the whole point of a Singleton design pattern is that it ensures exactly one instance
of something can be created and the other part that seems to go hand inand with it but it's not necessarily a requirement of a Singleton is that Singleton are generally globally accessible again it's not a requirement it just so happens that when people Implement them they're often done this way so now you might be asking yourself okay if a Singleton is one state and is globally accessible is this not the exact same problem and the answer is kind of sort of like you're on to something here right they are very very similar I'm not going to try and deny that but I like using a Singleton in this case as a stepping stone to be able to refactor code over time so by by moving to the Singleton we're able to program against an API and then from there in my opinion we can kind
of stop like what I would call stop the bleeding so that people stop using that Global shared state in the static class we can move away from that and start programming against this interface and I think this is a good opportunity for us to jump over to visual studio and start seeing how this works in practice on my screen I have a sample program that is going to be using this static globals class that I have here and you can see that it has three public static variables on it and at the very top we have this tiny little program that we're going to walk through because this program is going to leverage this class with these Global static variables so the first part that I want to talk through is checking to see where this is used because our whole goal here is to
try and get rid of as much of this as possible I'm also going to call out that I did try to make this example simple so you might be reading this code and saying well if you just want to make these things all read only doesn't most of the problem go away and you're absolutely right this is a contrived example so this could be a lot more complicated in real life than just being able to mark these things as read only so the goal here is really that I have a sample application and we're going to try and get rid of as much of this as we can so if I scroll a little bit lower in the code we have a class that's called my API client and if we look at the code that's used inside of this method called Fetch metadata async
we can see that we have the glob mobal Statics accessible right here on lines 57 and 58 what we're going to be doing is adding an API key and a username into the headers on this HTTP client from there we use the client to go fetch some data from a website a quick reminder that as I'm going through this stuff what the code is doing is not necessarily important it's really more about how we're using these globals and how we can move away from that so this is one spot where it's used and I wanted to include in this exercise another class that we're not going to be very interested in it's not really part of the core workflow that we might be focused on but it's also using these globals and I wanted to introduce this because I didn't want to trivialize the refactoring
process I wanted to show you that if you're trying to do this in real life and you have some other code that's also using the globals you could find a way to chip away at this over time I jump back up to the globals you can see that I had this extra public field as well I want to include this in the example Le because what we're going to see is that we can't get rid of this whole thing because I wanted to have a field that it's not currently used in the code that we're looking at but it might be used somewhere else in this bigger application so our goal is to get rid of these two things and at the end we should be left with just this one because we can't tackle it quite yet it might be buried somewhere else in
the code base and for what we're looking at we're not able to touch it right at this point so we were able to see the usages of these username and API key fields on this globals class and the next step that we want to look at is trying to see if we can pair this information out into an interface this is an opportunity for us to use some creativity so instead of just moving to a Singleton that's going to be called globals or something that's very very generic we could try to make this more purpose-built and the goal of this like I said is that we're going to be programming against an API so let's come up with something that makes sense for these fields being used I'm going to go ahead and create an interface called I API client config and it's going to
have these two properties that map to the two fields that we have up here this is going to be the interface that we have for our Singleton if you're trying to minimize the amount of refactoring work having these things paired up to be almost the exact same is very helpful it'll make the process a little bit easier to go through but you don't want to trivialize it either so if you're making more work for yourself down the road I think this is a good opportunity to come up with different names or just come up with the right feeling for the API that you want to be coding against from here we're going to create our Singleton I've gone ahead and created the Singleton implementation so I'm going to explain how this works in a little bit more detail the first thing to call out is
that I'm going to be using the lazy pattern for creating an instance and what's really nice about the lazy pattern is that this is going to enforce thread safety and truly this only executes only once so by leveraging lazy we can get a lot of the work done for our Singleton now what is the lazy implementation creating well it's going to make this API client config that I have down here so let's talk about that next this API client config implements the I API client config interface that we just looked at you can see that I've marked it as private so no one else outside of this class is possibly able to create this and the other thing that we're doing if you look at the implementation of these two properties is we're directly referencing the globals in my opinion this is an optional thing
that you can do I like doing this because in the meantime if you have a big refactoring going on what you're doing is making this almost identical to the current behavior if we're just referencing this then someone else being able to reference this username property is essentially just getting a pass through to the globals that means if you had weird Behavior with State changing out of order it's going to be identical as you're going through this refactoring because this is just a pass through however you could also take this this opportunity to instead go make this not touch globals at all and hardcode the things that you want to use here or put in the implementations that you need to the way that I've seen success with this in the past personally is that if I start moving all of the global access behind a
Singleton interface the only spot that's going to be left in the application touching globals is inside of these Singleton and for me that's a really nice way to have uh a clear indicator that I'm able to take the next step which will talk about in a little bit so now that we have our Singleton implementation we can go start replacing where the Global's implementation is used the first spot that we had is in this my API client implementation so I can go ahead and say API client singleton. instance and I'm going to put that all on one new line so it's more readable I'll do the same thing for the field that was used right below so now we have the two Singleton usages right here replacing the Global's usage and we had one more spot that we can do this and I'll go do
that same change here and now we have both usages replaced in the two classes and now you might be saying well Nick this is literally the exact same thing what was the entire point of doing this and I want to remind you that this is my pattern when you have a large refactoring going on in a big code base if you had something truly as trivial as this going to a Singleton might be just a whole extra step that you don't really care about but if you weren't able to go replace all of the usages of the user name and API client and you might have to leave those intact for a couple of different commits or possibly weeks or months if you need to do this incrementally I feel like this Singleton approach gives you that flexibility and a lot of what I try
to talk about on my channel is giving you real world advice not just theoretical stuff because yes we could go just jump to dependency injection right from the start of looking at this example but I want to walk you through some tools that you can use use hopefully in real life in complex scenarios and if we don't talk about these as potentially being more complex you might be thinking about this video If I Had simplified it a great deal and going well Nick said to do this but that's not even going to be applicable here we'd have to go try and make changes to a thousand files before we could even commit this and that's going to be nonsense in real life you can't get away with trying to block changes on huge refactors for weeks or months or anything anything like that you need
to find ways to do it incrementally so the entire goal here is that now we can start programming against interfaces in the spots that we were using the globals as long as we were able to safely go touch these things we can start putting the Singleton usage in place because it truly is the way I've done it here a direct replacement it's in fact referencing the globals as a pass through fundamentally all that's really changed is that the calling locations are now asking an interface for the data versus going right to the static class and that's because static classes just cannot have interfaces in C so let's go back to the code and look at the next step for refactoring this now that we have this code in place it's going to be working against an interface which is I API client config we can
start to do some interesting things with our dependencies so if we think about this code that's on the screen right now it doesn't really care that it's getting the implementation of the I API client config off of this Singleton all that it really cares about is that it needs that dependency so let's provide it in a better way let's provide it through the Constructor so we can work towards composition now in some cases the code that I have on screen right now with this new field API client config being instantiated by just accessing the Singleton and the Constructor and then leverage down here this might be the only thing you can do in the start and the reason I'm saying that is because the ideal situation is that we want to start passing it in through the Constructor once we can start doing this and
passing in through the Constructor and having it assigned here this means that we can start feeding it from higher levels in the application so this would be more ideal as the next step but you might find that all of the spots that you're making an API client you're unable to start passing this in as soon as you start making a change like this you're going to be in a situation where you're doing more refactoring to go now fix all of the spots that call this but truly this would be the next step that we want to get to however you might be able to do something like this in the interim to get you one step further you'll notice that the number of times that I'm calling the Singleton instance instead of it being two down here it's only one so this is a very
small step forward but it's still a beneficial step to take in our case we're going to to go all of the way and pass it in through the Constructor I want you to keep in mind that we have this other location that needs to be patched up as well but I'm going to leave that to a little bit later so I can demonstrate to you what happens when you run into a situation where you don't have all of the spots replaced all right so now we have our API client being instantiated this way but the reality is there's a calling spot that's creating this and it's not currently passing in this config that's because we just added as as a new parameter so if we go into this class called my business logic which we haven't looked at yet we can see that we're creating
an API client right inside of the Constructor here but the problem is that we're not passing in the config here so where does that config come from it comes right from the Singleton all that we're doing is slowly moving this problem of using the Singleton further and further up the call stack and I'm showing you this in incremental steps because at each one of these line changes that we're making in my opinion opion that's a net beneficial step forward because if I jump back into this class now my API client we can see that this class doesn't use the Singleton at all now we've fully removed dependency on that Global's implementation here and from the Singleton you can see how shortlived the Singleton was in this particular case but depending on how complex your application is it might be a little bit longer lived in
the my business logic class we're encountering the same sort of issue though so what's the Next Step well we just repeat exactly what we did you could go ahead and pass this in just like this or if we're looking at this code as it currently stands I might say well why is my business logic responsible for creating my API client in fact maybe I want to go push this whole thing back up to be passed in so this is an opportunity for you to make changes like that if you just want to continue passing in API client config because it's one tiny little step better in my opinion that's totally fine but I think we can do better in this particular case and we can just pass in the whole API client but when we do this like I said we just move the problem
further up the call stack but that's pretty awesome because further up the call stack is right at the very top of our application and that means that we need to pass in an API client and in order to make one come on co-pilot you should be able to figure this out it's going to make me type the whole thing um we're now able to do something like this that's close co-pilot you could have done that I know you could have we're able to have this and now the Singleton is only used at the very top of our application the goal of doing this is that we want to move all of our dependencies to be passed in Via Constructors and then at the top level of our application that's where we're creating them because there's an awesome pattern that we can use once that's in
place so we're almost at the point where we can even remove this Singleton but I did mention that I was leaving one more spot that's going to foil our plans for now and that's because this other class that we had at the very bottom here this one is still using the Singleton instance and I mentioned that I brought this in not because it's being used in our code currently but to demonstrate that this might be somewhere else in your code base and you can't get rid of the Singleton instance yet and that's okay because at this point in time we've gone and refactored some other things if I scroll back up a little bit to this API client this one we could go write unit tests for and not have to worry about them being interfered with by the static class maybe someone mutating things
or anything like that this one is entirely isolated and awesome to work with this one here still runs the risk that if someone could change any of these fields we could be in for some issues so the next step if we could and we're going to demonstrate that we can is that we want to go do the same exact thing with this class again I'm just showing you that you can do this in increments and as you do it in increments you're making the code net better at every step if we were able to we could do this exact type of refactoring that we saw earlier where we start passing in the client config through the Constructor and the calling locations that were touching the Singleton no longer reference it just keep in mind that as you start doing this and changing the Constructor you
have to go up the call stack and start modifying all the other locations and because currently in our code nothing else is using this we can go ahead and take that next step so all the way back up at the Top If I go check all of the usages of the instance property that we have for our Singleton it's only this one spot at the very top of our application this one line that we have is truly the only spot so we can take the next step to totally kill off the Singleton now in order to do that there's something cool that I did in the implementation here and that is that I made an implementation of the config that we want right inside so I'm going to go ahead and cut this out and then I can go ahead and delete this Singleton it's
now completely gone from our application and that means this lineup here is going to be complaining if I paste this config back in and make this public instead we are even closer to being done at the very top here on line one I can replace that Singleton with creating an API client config and that's going to new up an instance of this class here that we just dropped in and we can pass it reading online too at this point our Singleton is completely gone but we haven't gotten rid of the globals part but that's totally cool because this part's super easy to do now I can go replace this username up here with the right username I can go move the API key right up to here and now these two globals these fields on here we don't need them anymore I also realized that
I should have left a comment in here not that it's important but you should never do this there we go there's some Redemption don't ever do this kind of thing you want to use other techniques to pass in this uh secret information so just keep that in mind so in our application now we've effectively gone from having these two other Global fields on here through a Singleton to be able to slowly chip away at refactoring and once we've gotten through all of that we're now done with the usage of that Singleton we can't quite get rid of this globals because I left in this field that I mentioned at the beginning of this video that we're going to pretend this is used by some other parts of the code but what you would do is repeat this exact process we looked that in order to
start chipping away at whatever is left inside of globals you want to get to a point if at all possible where you can start passing things in through the Constructor and I want to round out this video because if you go to implement this type of thing in your production code and your codebase is a lot bigger than this simple example that I was just showing you what you're going to find is that you start having hundreds or potentially thousands of lines of initialization code right at the top of your program and it's cool cool because the rest of your code became more testable it's using composition and interfaces and that's all really nice but the entry point to your application is now a complete dumpster fire if you have hundreds or thousands of lines of just neing up classes and stuff like that it's
nearly impossible to navigate but the solution to this in my opinion is using a dependency injection framework we can start to organize our different initialization code into different modules organize things more clearly and have a better understanding of what's going on instead of trying to scan hundreds or thousands of lines that are stacked together to go see where something's created we have dedicated modules or classes that can do that for us I'm going to go change this code we're going to use autofac to do it but because I'm not using asp.net core I'm not going to be showing you the Microsoft dependency injection framework it's done in almost the exact same way it's just that the syntax is different so you can go ahead and apply the same exact Concepts that I'm going to show you with autofac and use that with the Microsoft built-in
dependency injection framework what we want to be able to do is have these different things that are dependencies created almost automatically through autofac or a dependency injection framework in autofact that's done through something called a container builder in autofact the way that we can start to organize our dependencies and have them registered to our dependency container is using something called a module and to do that we override the load method that takes in the container Builder and we can start to do our registrations inside here the three things that I want to be registering are these three classes right here from lines 5 to 7 the first one that I'm going to do is the API client config class I'm going to register that type on the autofac container and then I'm going to say that when people want to resolve it they can only
resolve it by the interfaces and not by the type itself the other thing that's really important and really relevant if you think about all of the usage of this config that we had before is that it's going to be single instance here does that sound familiar it's not going to be a global constant it's not going to be a Singleton implementation through the design pattern but we're using dependency injection to say that anytime someone asks for an i API client config they will always get one instance that's created see there's different ways that we can get a Singleton registering the API client is going to be similar in this case I'm not going to be doing it by the interfaces and that's because when I was refactoring this code the dependent spot my business logic requires my API client class I could have extracted an
interface and then used that here but we're not going that far in this refactoring and the other thing to call out is that in the current implementation I can get away with single instance that's what I would expect here however in different cases you might be saying well that's not going to work because it's not thread safe or I need to make sure that there's different usages of this because I can't share the state in different spots that's for you to decide and figure out but I'm just explaining why this part looks different than this part right here and finally the last spot that I'm going to do is add the same thing in here for my business logic it's going to look the exact same as this pattern for the exact same reasons we never end up resolving an interface called I my business
logic doesn't exist but if we look back up here on line S we can see that we're using that directly here and on line 8 directly as the class so now that we have our module we need to register that to The Container this is a pattern that you can use for this and I have other videos which I'll link right up here and you can watch that now on different registration patterns to go scanning for assemblies once we've registered the module or the other modules that we need we can go get a container co-pilot so awesome I barely have to code anything anymore the next part after we have the container as co-pilot suggesting is that we want a lifetime scope I'll just press Tab and let it finish finish this entire video for me yep co-pilot's right again we want to be able
to resolve my business logic off of this scope and all that we're going to do is the exact same thing where we're getting that result and again writing that result to the console and that means that I can go ahead and highlight this code and delete it we now have dependency injection done for us right up here all of that handles getting this module registered and in your application you might have a bunch of different modules you can go organize those by feature or however else you see fit if I go back up once we have these modules registered the only other things you need to do are build the container get a lifetime scope and then resolve the first part that you want to enter to in your application and that is how we get dependency injection to work starting with these static Global
variables that are potentially really dangerous in our application remember that when you have stuff like that other people can potentially mutate them or those things might be changing behind the scenes and all of the different spots that use those static globals could be at risk of having that stuff changed under their feet from there we went to a Singleton design pattern and for a lot of people the Singleton makes them wildly uncomfortable and that's because it's almost the exact same as having these Global Statics I get it every time I see a Singleton en code I get goosebumps all over my body I want to get it out of there but I just showed you that I have a use for it and I have used this professionally in very big code bases where we had tons of these Global Statics this is literally a
pattern that I have picked up for my professional experience and just applied to this really simple example once we have the Singleton in place we go back through our call stack trying to get rid of the Singleton wherever we can and this allows you to incrementally refactor by composing your objects of the dependency instead of just directly accessing the Singleton itself and finally once you're in a position where you can get rid of all of those Singleton references you can go over to something like dependency injection because all of your dependencies are able to be passed in through the Constructor I used autofac in this example but you can use the Microsoft one or any other implementation of the dependency injection framework that you'd like to work with if you found this video useful I'm not going to link you to another one but I
do have a course on dome train that's a boat refactoring it's about 5 hours long and it has all sorts of examples of different techniques to walk through for you to refactor things I'd highly recommend you check that out if you found this useful because this particular example is not in that course but I think there's a lot of other useful examples that you can leverage in your own practical experience I'll have that course linked in the comments below as well so if you're interested you can go check that out on dome train thanks so much for watching and I'll see you next time
Frequently Asked Questions
What is the purpose of using the Singleton design pattern in this refactoring process?
The Singleton design pattern helps us ensure that there's exactly one instance of a class, which can serve as a stepping stone to refactor away from global constants. It allows us to control access to shared state and provides a way to gradually move towards better practices like dependency injection.
Why are globally accessible variables considered dangerous in a codebase?
Globally accessible variables can lead to uncontrolled access and modifications from different parts of the application, which can create unpredictable behavior, especially in asynchronous code. This lack of control can result in hard-to-debug issues, such as flaky tests that pass or fail inconsistently.
How does dependency injection improve the code after using the Singleton pattern?
Dependency injection allows us to pass dependencies through constructors, which promotes better organization and testability of the code. It helps eliminate the need for singletons by making dependencies explicit, leading to cleaner and more maintainable code.
These FAQs were generated by AI from the video transcript.