How to Avoid Singletons: Dependencies, IoC without a framework (C# & Golang examples)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey what's going on my name is Izzy and today I thought I'd talk a little bit about dependency injection and inversion of control and how you can use that to eliminate global variables and Singleton's and some of your code bases so we're gonna be going over two code bases today the first one on the right here is a bit of go code and this golang code is some pretty typical HTTP handling code just sets up an HTTP server and has an API and the logger so it logs out information about the application on the left here we have a bit of game code which is written in c-sharp and this is also a pretty typical piece of game code in which we have a game manager and the game manager does things like manage whether or not the game is paused then we have a singleton there which we're gonna eliminate we're gonna take out that singleton and replicate the functionality okay let's start off with a quick overview of how this code works so here's our main function and you can see that we have a logging package and we're calling init logger and init logger is defined over here in the logging file as you can see that sets up the logging instance which is a pointer to a logger it just creates a new logging instance and gives it a prefix which is a string we pass in here it's just called log so the logger itself is super simple it's just destruct the prefix is a field which is a string and it has a method on it called log F so in c-sharp you might be used to this method itself being embedded inside the logger or in java as well and basically it's just a method on the logger that you can call to log out some data it locks the prefix to format the arguments and the newline so a very simple printf function so we're initializing that pointer to be to point to a new logger instance and then you can see that we're logging out the server starting on port 3000 setting up an HTTP server with the basic Handler jump over to the handler you can see it also relies on the logger and we're getting the request URL printing that out and printing status okay so here I have it running we can see the server started on 3000 and if I go ahead and jump over request for localhost / wombat you can see that we get slash wombat on the other hand if I request for our slash dog then you can see that we log out slash dog pretty simple and it seems to work absolutely fine so let's talk about why you might not want to do this so let's say we wanted to test our handle request function to make sure that it gives us the correct status code maybe it does some things that we want to test make sure it's handling some URLs directly and that this this little function is is working so that when we go to upgrade it and increase its functionality or change or over factor the code we're a hundred percent sure before we deploy that it's working as it was working before so we just run the test so here's the test all it does is create a new recorder and you request it request get slash wombat runs the handler function and checks that the status is okay super simple just make sure that has status code 200 and if it doesn't then we fail the test so this is super simple stuff and at a glance it looks fine our hey our editor isn't giving us any any problems our go compiler isn't screaming at us nothing looks like it should be wrong here but when we run the test you're gonna notice that we get a seg fold so if I if I run the test you can see that we get an invalid memory address or a nil pointer to reference or in C++ terms or C terms it would be segmentation holding the whole thing with crash and burn so why is this why do we have a nil pointer to reference well it's actually quite insidious and if we if we did debug the test we can drop a debugger right here we can drop a breakpoint and we can run the debugger okay so we've hit the breakpoint in our debugging and you can see that we've gotten to the part where we log out the request URL nice and simple and there it is we have the test runner it's failed we've got a panic and if we let this go we can see that we're at a fatal panic and the entire thing crashes and burns the reason we crashed is because the logging instance here a longer instance was nil when we went to handle the request because we actually didn't call in our tests in it logger so the environment wasn't set up in the test and in order to fix that what we'd have to do is import di slash logger slash logging and call logging dot init logger and we'd have to give it some sort of prefix which doesn't matter so this works but there's two major problems here for one this isn't testable if we want to test that the API does actually logout the correct thing it belongs out of the URL there's no real way to do that we have to create an event in our logger and hook into it or maybe we'd have to log to standard out and check the standard out or something like that it would be a total pain and on the other hand let's say that our log are dependent on say an AWS connection in order to for the logs to some sort of query then this would be almost possible you'd have to set that up in your testing environment every time you wanted to just test the API you'd also have to be setting up the logger which is just pretty ridiculous why should you have to set up all the loggers dependencies just to be able to test the API let's figure out how we can do this a little more succinctly and testable testable testable e in a more testable way okay so looking at our API we can easily say that the API depends on the logger as in order for the api's handled request function to function correctly it requires the logger instance we've already seen that because when it didn't have one it crashed and burned so we know that handle requests as a function depends on the logger instance so how could we inject how could we give the API the logger that we want to use for that particular call rather than having to create some sort of global variable that we can't really manage properly well the first thing we're going to do is just get rid of our global variable we'll remove it and instead we'll set up in it logger to instead create a longer creates a new logger so this is going to return a pointer to a new logger and we can simply change this logger instance to just return new logger so this all checks out fine we now need to go into our main go function and remove the logger setup and instead we're going to simply create a new logger logging dot create longer and we'll give it a prefix of log like we did last time and then we can change this call to simply B log or log F that's great but what about the API now there's a couple of ways that you could do dependency injection here that you could inject the dependencies in to handle requests so one way to do it they may be slightly naive way would be to create a function around the function which returns that function so this is what's called a closure a function which returns a function and the dependencies are going to be injected via the closure so what we're going to do is change handle request to create Handler and we're going to have that take in a logger login dot logger and return an HTTP handling function and then in the body of the function will simply return a function which performs our request then we'll sit the logger dot log F there and we can comment this as great handler generates an HTTP handler using the provided provided blogger okay so we've now got a function which returns a handler which takes in a logger now this is okay and this works but there is another way that we can do this that may be a little more useful to understand and also it is a little bit more familiar to those of you who write in C sharp or Java so instead what we'll do is we'll create let's say handler which is a struct and what we'll do is we'll have that structure have an instance of a logger so we'll say logger is logging got logger and it'll be a pointer to logger then instead we'll take out our function which returns our function and instead set this new function to be a method of the handler so we'll say H star handler handle request and instead it will say H dot logger dot log F all right now all we have to do is create a constructor this isn't strictly necessary but it does help will create a crate handler constructor which takes in a logger and returns a handler and then returns a new handler the logger set just like we did for the actual logger itself ok then in the main code function we will simply call a PID a handler passing in the logger and that will return the handler which we can then use in our HTTP server okay so this does still work the same way if we go ahead and go run mein dekha we can see that it all runs perfectly fine and the logging works however our tests actually will still break if we go over to our API test remove the init logger call and create a new handler we still have to create a logger which is kind of a total pain if we don't create a logger then we will have a nil pointer to reference and yeah it's still not the best because what logger do we give it if we give it the logger that it needs it still might have those dependencies that I mentioned before it's still a total pain okay so going back to our API let's really think about it we said that the api depends on having a logger but it doesn't it only depends on having the log F function and the API doesn't actually really care what that log F function does it doesn't really matter to it as long as it has some function for some logging it doesn't really care it's got a signature it's called log f it takes format and then any number of arguments to that format string but other than that it doesn't actually care what it does why should it it's not the api's job to care about what the logging actually does as long as it has a logger so this is where an interface comes in let's define a contract saying that as long as it has this log F function I don't care what it actually does we can do that with an interface so let's create an apt longer interface and put this log F method string sorry method signature into our app logger as a function signature then we'll say app logger defines the contract fuel methods for a logger in the application okay so now we have this basic interface it doesn't actually do anything it simply defines some method signature it doesn't implement it then in API code we can simply swap out logging dot logger with logging app logger and we can do the same in our constructor and as you can see my editor won't complain and my compiler won't complain at all about main dot go or any other parts of my application because of course they still satisfy that that that API they still satisfy that Apple her interface so this is great this allows us to write a fake logger for our testing and this is super simple all we have to do is create a fake longer which is a struct it can have absolutely no dependencies no prefix absolutely nothing required and it simply implements that one interface so we'll say FL star fake logger and again we need the log F function which takes a format string and any number of arguments do nothing there you doesn't have to do a thing and we'll say fake logger fake logger is a dummy logger for testing and then we'll say log F does absolutely nothing simple so now the API doesn't care whether it gets a logger or a fake logger because both of them have this log F method that satisfies the app logger as long as it gets an app log er it doesn't care whether it's a fake logger or another logger or any other longer and it makes it much easier to write our test now all we have to do is create the handler so we'll say API R will say actually handler is equal to create Handler and we'll pass in a new fake logger logging dot fake logger like so sorry I spelled Handler wrong and I also spelled fake larger huh going left here today then when we test our code will simply call handler dot handle request like so and as you can see we've created a new API handler right so in our API test we've called this crate Handler and instead of giving it the normal logger a pointer to a normal logger all we're doing is giving it the fake longer a new fake logger it's empty it doesn't do anything it's just a dummy logger for testing and when the API gets this logger and we call handle request the logger it's going to have injected into it is just going to be this fake logger which does absolutely nothing and so now we don't have to test the logger when we test the API we have a completely blank dummy logger and if we want to test the logger we can actually have this logger return some string or hook up some event or something like that so we run the test no crash no nullpointerexception because we've given it a longer and we didn't have to set up any dependencies we don't care about any environment we don't have to do anything and we get no crash our test just passes perfect just like that I really hope that helped you understand and this is probably a perfectly fine point to turn off the video but if you're still confused and you want to see this implemented in a language that's more familiar like c-sharp or Java I'm going to implement it in c-sharp now just to really hammer this home but if you are gonna stop watching here I really hope it helped and thank you so much alright let's get into the c-sharp implementation righty so jumping over to c-sharp i've got an example of something that you might see in a video game link unity or some other sort of make this a little bigger there we go some other sort of game engine or even if you're rolling your own so here we have a pretty simple app that makes two weapons and two orcs and each quirk can attack so or quanta tax the position of the second orc that works were pretty simple they just have a weapon with an x and y position and then their attack method it checks the game manager so it gets the instance of the game manager checks if the game is paused and if not it attacks pretty bad practice if you're if you're if you're paying attention but you know you may have seen this kind of code a lot even in the unity official tutorials they do this kind of stuff it's pretty bad so the interesting thing here is the game manager it's basically your typical singleton pattern basically what this does is create a instance of itself the game manager which is a static instance so this class has a static instance to its own class and if this static get manager which returns that instance so there can only really ever be one active instance of this class at a time unless for some reason you created another one in which case it wouldn't really matter but again this is pretty bad pretty untestable because this game manager very likely has a ton of dependencies and every time you just wanted to test out the orc in isolation you also have to make sure that the game manager is set up and that would be a total pain because if you wanted to just create an empty scene and drop an orc down and see how they react them whatever you'd also have to set up the game manager and everything the game manager needed so let's do this a little bit better we're gonna start by creating let's see I game state CS and we're going to put that under our normal namespace and we're going to create a public interface called I game state and inside this interface we're going to define a method called get pause state and it will return a boolean like so then inside our class we're going to have a private reference to an eye game state private eye game state and instead of asking for the game manager if it's paused we're going to refer to this game state dot get pause State okay and then in the constructor we're going to take an eye game state we'll put that at the start I game state and we'll set this dot game state equal to game state and are so forgot this dot x equals x and it's not Yui so here we have our eye game state now and our or instead checks for get pause state so in our game manager what we're going to do is have that implement eye game state okay so now we're implementing the interface so we want to get rid of the singleton stuff and instead I'm just gonna run public what was a full get pause state to implement that I game state and I'm gonna return this thought is paused cool nice and simple so this isn't a singleton anymore and our orc simply needs a game state dependency injected into it so inside our app we'll just want to create a game state so we'll do games game manager manager equals new game manager and then when we create our orcs all we have to do is make sure we give them the manager and of course an exit light position so I'll just give them ten twenty we manager ten twenty five forty there we go so our orcs now are given their manager they have a manager injected into them and all they really need is the eye game state part so you can see I game state just implements get ha state the orcs check that the pause the game is not paused obviously this should actually be if not get pause state and now they have that game state dependency injected so if we were doing it and we wanted to quickly spin up into work without having in a giant game state we could pretty much pretty easily make our own implementation of it by simply creating a public class called fake state and inside that we can implement high game state by public pool get pause State and we can just always return false and then instead we can simply swap out the manager for the fake State if we want just like that and so this is pretty good this is a pretty easy way to stub out that dependency and now we don't have to worry about the orc not having a game manager that's fully fledged and all of its dependencies are set up and you don't have to have a singleton floating around in global namespace which is kind of a bad bad practice in my opinion so the unity the unity official Docs tend to encourage uses of Singleton's things like that so I just wanted to show you how you can avoid using a singleton just using normal everyday c-sharp code that it may not be the most intuitive to you so this is the practice of inversion of control so the orc doesn't actually care what the game state is as long as it has a method that it allows it to get some positive and that's all again maybe it's a little bit contrived but I hope you get the point and why it can avoid Singleton's all right the video has gone on long enough I think this will hone it in and I really hope that you found this video helpful and that you can implement this in your next projects and you start thinking about dependencies and how your objects depend on other objects if you have any questions please leave them in the comments if you like the video drop a like and I'll see you in another video
Info
Channel: Izzy MG
Views: 2,834
Rating: undefined out of 5
Keywords: Dependency Injection, Singletons, Good Programming Practises, Golang practises, C# practises, global variables, C# singletons, golang singletons, golang dependency injection, IoC programming, Inversio
Id: muTijwQmuaQ
Channel Id: undefined
Length: 18min 55sec (1135 seconds)
Published: Sun Jan 19 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.