Learn C# with CSharpFritz - Ep9: SOLID Principles and Dependency Injection

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[Music] what happened there oh my goodness good morning good afternoon good evening whatever time it might be where you are out there welcome back to the live stream my name is jeff fritz and this this is c sharp with c sharp fritz we're here live on the visual studio channel it's all nice and sunny outside it's it's always sunny in philadelphia and uh we're getting ready to talk about c sharp some programming techniques with c sharp and today today we're going to get into a little bit of the design questions that we've had hanging out there we've learned a bit about how to use the programming language but what's a good way to put these things together that we've learned so that we can create better better classes better object models better interactions so that we can take create effective applications that that are maintainable um performant maybe and allow us to make changes without risking breaking things so that's what's on the agenda today let me say hello to the chat room here good morning chat room how are you today source good is here looking forward to the stream loves solid thank you so much our friend legola codesit is here so good to see you sourcegoot and layla codes that are both mvps visiting us and their live coders thank you so much for tuning in cushy mushy good to see you hootenheimer good morning to you in northeast wisconsin got your first batch of snow oh my goodness and it's it's only october and you already got snow in wisconsin that that sounds like colorado weather up there sending you sending warm thoughts to us hootenheimer wasn't ready it's only october well yeah yeah shy sharp good afternoon to you twiliane in brazil hello hello eckerd yes we are going to get solid oh my goodness uh in is that antique antique raman in sweden hello to you salvador sarpy in chile good to see you thank you for tuning in but do we have where else do we have some folks tuning in from the the uh bombatenda from the gambia the gambia i'm not sure what that was uh ponce de leon in peru good to see you hello hello thank you for tuning in um really appreciate what else do we have folks tuning in from let me um make a change or two here there's a thing there we go um it's great to always see all the folks that tune in and join us from wherever you might be um and that's kind of the power that we have here on twitch the power for anybody anywhere to be able to dial in to listen have a discussion and uh hopefully we learn from each other together and we're gonna have a good time maybe we'll tell some jokes maybe we'll we'll learn something we'll stumble into some interesting code and uh figure some things out together right uh is that dana sarg in romania good to see you thank you for joining us so let me get some music playing in the background we'll check out the github repository i'll make sure that you have a link to the youtube playlist where all the rest of the videos in this series are available and of course if you're watching the recording those links you're going to be able to find them just below me here in the description links to the interactive notebooks that we're going to be using links to the github repository if you want to go in and ask some questions send a pull request with some suggestions you can do that it's all available to you just below in the description and if you're here watching live we're going to have those links appear in chat in just a second we play um stream beats from our friend harris heller this is free royalty-free music that you can listen to on your youtube channel on your twitch stream that anybody anywhere can play without fear of any kind of copyright claims against you so this is being played from spotify and this is the synthwave playlist and we'll get this plane in the background for a little bit of groovy music to kind of fill the gap when we're not talking so hello to you master cram in quebec good to see you thank you for tuning in and there's a message popping up also in the chat from stream elements to make sure that folks know this is a beginner friendly talk show format where we're not directly teaching the whole time but we're going to be answering questions and interacting with chat and if you have questions on the recording submit those comments below and we'll be happy to come through and answer them later making sure that audio levels are right i think that'll work so um let's head over to the github repository and i'll drop that link in chat for you so you can see where you can get involved here we'll head over there we go and i will give you that github link there it is to access the samples this is github.com c sharp frets slash c sharp with c sharp fritz the link's there in chat like i said the link is just down below here on the youtube recording hello to you swanee 93 in india good to see you so we've gone through we've covered eight topics already and they're all available to you here inside the github repository you can go into the notebooks folder and you'll see all of the notebooks that we've gone through to this point and well that's nice these are these are jupiter notebooks that you can click through and look at you can follow along if you're if you're watching at home or wherever you might be you can follow along in your favorite browser by clicking the launch binder button here and it will launch that notebook from the binder service and you'll be able to tinker with the code along the way be able to read the documentation and any of the links that we put into the documentation or any links that we research along the way here that we want to make sure we cover we'll add those to the documentation so that you can jump out from there and learn more about the topic along the way so um i have this downloaded and running locally on my machine using jupiter lab um if you want to download if you want to clone this repository there's a dev container here so that you can launch this using visual studio code without having to install any other tools it will run completely using containers on your machine on your workstation i'm seeing that uh that message is coming up just a little bit too quick for my liking here i'm gonna open up that beginner's message a little bit so it takes about 20 minutes to come back next time so so we're going to talk about design principles we're going to talk about the solid design principles that are out there for folks to use when they structure and decide to build their applications and and put together pieces of functionality using c-sharp so this is very much focused on using the c-sharp language and we're going to ensure that all of the samples are runnable and we might tune them along the way make some changes and ensure that there's some interesting things that we can do with them so that maybe you want to use them later so i'm going to jump over to jupiter lab and this is a jupiter notebook so that's a notebook that runs with python that has c-sharp and dot-net sitting behind the scenes that allows you to run all of that c-sharp code live in the browser so we can make changes we can throw errors and it doesn't break things it all runs immediately in the browser and outputs immediately uh any kind of response codes that we need to take a look at it is it thank you layla it i i think this is very cool so we're we're using this to learn more about um learn more about c sharp and we're almost up against our november series where we're gonna break out of the browser and we're gonna start getting into um building real applications and and it looks like we're gonna be doing blazer first and we're also gonna have a c sharp nine update stream coming up in november belazio let me bring up that comment that's a pretty good comment for us to touch on here there it is just discovered yesterday that solid even exists don't know too much about it let's let's get into this let's talk about this so solid it is a series of design principles design patterns that were initially proposed by robert c martin in his 2000 paper design principles and design patterns developers naming things are hard but if you're going to write a pat a paper about design principles naming it design principles is a pretty good start later these five principles the acronym solid was coined by michael feathers and there's links to both um robert martin's paper and to um a wiki wikipedia article that talks about michael feathers and the solid coining so um these are principles now i want to make sure that folks understand there are there are some people who are they're quite dogmatic in their approach to these they're they feel that you must abide by these five principles like like all rules in in engineering um and honestly when we think about software software walks that fine line between engineering and art there are times to adhere strictly to the rules there are times to bend the rules and there's times to break the rules for new developers these are some good suggestions these are some good things that you're going to want to investigate and learn more about so that you know when it's appropriate to bend and break those rules you'll find senior developers architects will regularly bend and break those rules i recommend you adhere always to the number one feature requirement of software development ship usable software if you don't adhere to these principles and you ship software that works that is phenomenal you have you you have folks that are using your software you have customers you have folks participating in your applications that is feature number one if you use these principles and it makes it easier for you to maintain and grow your application to grow your system that's even better but it's not a requirement to have successful software so that's right our friend dev chatter um he's another live coder that you'll find here on twitch wrote a whole series of uh pup produced a whole series of videos covering each of the solid principles and we're going to go through them quickly today and i say quickly because we're going to spend two hours and cover the five principles and also talk about dependency injection at the end um leila you're right you're very right let's bring up leila's comment here so that folks can see there are two camps dogmatic and pragmatic both at their place some folks prefer a pragmatic approach some folks prefer a dogmatic approach each is can lead each one of those can lead to success it's up to you to figure out and your project team to figure out which way will work best for you so um we're going to discuss and we're going to get in here and there might be better ways to do things along the way we want to make sure that we are open and encouraging to beginners who are following along with the session so everybody's comments are welcome but we don't want to get too advanced we don't want to add too many features into the mix here but discussions about the pattern are certainly welcome so um house bisook um i don't want to get into folks doing advanced discussion around the topics and keeping away and intimidating potentially intimidating beginner developers who might have questions about the patterns so pre oh my gosh priyank is that priyanka p um 10 years experience and still following fantastic thank you for chiming in there appreciate that so the first of the acronyms solid is an acronym right solid stand for five different principles that we're going to explore through a series of demos and we'll write some code along the way to test and tinker with each of these demos maybe take them in directions that weren't initially considered lithics has 18 years of experience of being a beginner developer if you do the same beginner tasks again and again and again have you really become an intermediate or advanced developer yeah so um so let's talk about the single responsibility principle the s in solid a class should have should only have a single responsibility that is only changes to one part of the software specification should be able to affect the specification of that class now that's that that's kind of interesting there and we'll we'll simplify this a class should have only one reason to be changed or rewritten conceptually this means that you should have many smaller class objects that you can use to reference each other to perform a task right gosh smart guy who wrote that there that would be me so i've seen folks that create this this model that the god class right the utility class that that is able to do all things it is um it is omnipotent it is the class that does everything and that's kind of a problem when you have a very large class that that is capable of doing everything inside of your application that class typically resides in one file and now whenever you need to change some part of your application everybody's changing that one file so you start to run into collisions and issues and right you're gonna have concerns as you're editing that same file to make changes for what might be very different parts of functionality i'm going to use a logger example through our notebook here so that we can interact and use something that i think we're we're all going to be pretty familiar with so let me take a look here at a couple of the comments in the chat room um mastercram asks let me queue up a couple of these comments here [Music] there's a couple of really good questions and comments here what is a beginner developer isn't design pattern already an advanced thing so this is a little bit more advanced however discussing these patterns implementing these patterns i think is a little bit more intermediate we've gotten out of the basic features of the c-sharp language and we're learning how to put those together beyond in our in our previous uh sessions we learned about interfaces and abstract classes well what's a good way to use some of this object-oriented notation and structure that we learned how to create so we're getting into that intermediate discussion here there is a github command there you go that you can use to access the the notebooks and and content that we're going to be following along here in tinker with go ahead and make changes break it and and it'll report errors appropriately to you in the notebook without affecting anybody else it'll be okay um nick the first one chimes in it's always good to rewatch stuff like this i find that listening to the explanation of other developers helps you gain more insight i i completely agree with you nick this is that's a really good point listening to other folks describe these and even taking some time to describe it yourself write it down on what you think of some of these topics um and revisiting that description later after you've had a chance to implement you've gone through a couple applications it's going to help you learn and be more productive um so naloth asks is this a book that's not a bad idea after we get through a couple more of these it's not a bad idea to pull these together and make this a little bit more of an e-book type of content so not a bad point ancient coder that's very wise advice technically everyone is a beginner at the most senior as the most senior devs will be beginner in some technology or another not everybody's an expert in everything so we're always learning and uh you can always learn something new great points so let's talk about a logger object for this for this example and if we were to create a logger object right maybe it has these five tasks where we're gonna create log files on disks we're gonna name and rotate so that we write to different log files on different days so that our our application writes writes log content for today in the in the october 26th 2020 file and maybe tomorrow it writes in a october 27th file etc and so forth we need to have methods so that you can write log messages into that file maybe we want to have some way to format the log messages so that it has a date stamp maybe at the beginning or maybe there's some information that we want to carry along some meta information we want to carry along about our log messages and we want to format that appropriately and of course we need to close the log files so that it can be accessed in red by other systems so we could start off with a massive simply amazing logger class that is just called the logger and i've just allocated some simple methods to do those those things that we've described create new files name a new log file and we're going to name it after the the the current time in the utc time zone all the way down to the hour dot log and we'll have a method to lock messages and we'll have a method to format messages here we'll we'll close the log file appropriately and when this class is disposed as it's being cleaned up we'll make sure we close the log file if if it wasn't called already so very simple and i've purposely not included any file management information here i don't write that the implementation what it actually does with the files that's a that's another discussion for another time how you interact and write files and interact with files but for the purposes of this design pattern and discussing this pattern i think we're we're okay here with this level of interactions um [Music] so what i'd like to do is really realistically look at this and and think through there's a lot of places right and our logger is very simple create a new logger and we're going to say logger.format error detected and dispose and right we display that and it puts out a nice format here that has the the time the and the message that i i placed there right and it would write that to disk theoretically right in in this sample and that's that's simple that's basic right and it's a very complex class that that does many different things here but if we want to start start changing if we want to change the format of our messages or if we receive a requirement that says hey jeff we want to we want to centralize our logging we want to push that to a restful service somewhere out on the internet where we'll be able to to collect those because maybe we've got distributed offices that that folks are interacting with our application at we want to be able to centralize all of our log messages we're going to want to change that interaction well now now i've got to completely rewrite this entire class the single responsibility principle says well let's let's break that out because these are in fact different different responsibilities of the log operation let's let's put those responsibilities into different classes and we can put those classes then into different files so that when we need to refactor when we need to change that operation so that we still provide the same facade we still provide the same api to the rest of our application but the way that we code and support that logger object is composed of these smaller objects each one of these smaller objects helps to enact that that logger object and provides each one of those features then when we want to change just the naming we have a place to go to do that excuse me um is that devin is it devin goebbel or is it deving obel i think it's devin gobel um this is a good point here solid is made up of principles and they are applicable to any technology we're showing in c sharp today you can apply these same object-oriented principles to javascript typescript python your choice um it doesn't matter what language you're programming with but we're showing with c sharp today and they can give you a leg up when moving from one technology to another spot on well stated thank you for sharing um so let's simplify let's start to separate out these responsibilities so perhaps we have a file logger class to start that knows how to manage a file it'll create files it'll write messages to the file and it knows how to close and clean up the file and i just added a couple of display messages here that instead of actually interacting with the file system it just outputs a little bit of a message for us this way this way we can see that oh it it's doing those things when we go to log a message here inside of the notebook continuing perhaps we have a logger file name manager and this is the one class that knows here's how we're going to name our log files so name new log file and it goes and calculates that file name appropriately we're going to display it just so you can see what that file name is and return it to the to the calling method so we can interact with it and we'll create a logger formatter class next that knows how to format that log message so it'll receive my raw message that i'm going to write and i'm going to prefix that message with the a timestamp a utc based timestamp and the raw message that was sent so i have this nice formatted message i'll display it so that you can see it here in the notebook and return that message to whatever the calling method was continuing now now i'll have that composed object instead of having the the omnipotent the logger class i'll have a logger manager class and i make it disposable so that we're sure we clean up when we we release all of the resources for our file interactions so now this class becomes a lot simpler now this class is delegating appropriately to those child classes these three child classes the file logger the logger file name manager and the logger formatter so that we create those when this logger manager is created and we identify a file name appropriately when we start create that new file when we go to interact with it and when we log messages we're going to reach into that file logger object and say write this message out to disk we'll we'll format it appropriately and write that formatted message to disk we'll dispose and clean up our files appropriately so i still have the same syntax var logger equals new in this case logger manager display logger format error detected logger dispose i'm doing the same thing except now i'm saying log message and i don't have to do that format it's doing it internally and for me and logger dispose and i end up with these messages being output here's the file name that's being that's been decided upon created log file and it's got my file name appropriately it's formatted my message wrote message to disk cleaned up and closed log file appropriately so we've got very simple series of objects that now drive the same features and ernesto is is spot on here in the chat room um we are still coupled to the file infrastructure but we are only at the single responsibility principle this is our first refactoring spoiler alert ernesto is is pointing us in a direction that will help us in the as we go through the rest of these principles so um so we can change the format inside of our logger formatter class we can change how we name our files using the logger file name manager class and we can change the destination if we want if we don't want to write to a file logger well we can build another logger object and bring that into our logger manager class and use that instead okay more about that last one in a bit but logger file name changes log record format changes let's talk about the log format changes log record format changes and logger format that's going to take us into our next principle the open closed principle and this is one that can get a little bit tricky and this is one that i think folks will refactor ad nauseum so that they can um so that they can make things um it as simple as possible and this is where that balance between pragmatism and and dogmatic approaches comes in the open close principle states software entities classes modules functions etc should be open for extension but closed for modification so this means that having a generic method that knows how to interact with and provide the business value is one thing and be able to inject additional business rules will help will help to change that behavior but provide support for new features it it means that you're only going to create new features by writing new code you're not going to go into some existing code and change those business rules and you'll see this with folks adding if else if else if if else if else if else if statements or a switch case statement and they add more case statements into their logic what if we can change that by introducing and passing in classes that do that in in this case do that formatting for us and hand off that operation to additional operations um not getting into diagramming today master cram um boon up we're trying to stay with some with simple class design interactions so uh methods could certainly be done but we're we're staying a little bit simple just so we and and purposely adding additional code to show um interactions that are easier and bigger for beginners to be able to see the differences between um so we have our file logger object and that's nice and we have our logger file name manager but if we introduce an interface i logger formatter that will format the log messages that we're going to provide well we can define an initial one here that we've already got called normal logger formatter implements the i logger formatter interface we've already seen that interaction with with interfaces and how you implement interfaces in our in our previous session um and you can find that recording over on the youtube playlist exclamation point youtube so you can see that or if you're watching on youtube click the playlist over on the side you can go back to that so now i have my log message method but i can delegate right remember we can provide overloads to these methods i can provide a second method that receives a logger formatter and will then use that to format the log message and by default so i have the same behavior i'll default that to the normal logger formatter that puts the um the date and time at the beginning of the string followed by the message now that's that's not bad we're going to get the exact same thing written out log message error detected and it behaves the same way so that's not bad but if i want to if i want to enhance this if i want to enhance this so that it knows how to log exceptions i can provide an exception log formatter that will appropriately go and format our messages appropriately to handle exceptions so i'll implement ilogger formatter again this time when i construct my formatter i want to take in some exception object stash a copy of it here in this private variable and now when i implement the format log message method i'll include that that date stamp and i'll also report an exception of type whatever that exception type was um and whatever the message of that exception was thrown and whatever message we sent into the log message method so i've i've now expanded the capabilities of being able to log using my simple logger class by introducing an additional method an additional class that implements this method and we can go forward and use so we're still using the same log message right logger log message and i'm introducing my formatter object here with information about that exception that we need to handle everything is on fire so error detected everything is on fire so it created log file and here's the new message that it wrote timestamp exception of type exception with message everything is on fire logged error detected so think about this inside of your applications as you're building and working with expanding the facilities expanding adding new features instead of maybe doing an if case right if else or switch case types of statements being able to pass in maybe in this case a formatter or maybe there's a different business logic for different scenarios that you want to be able to handle being able to pass in and send that business logic in through a common or a shared interface is going to help you to be able to enhance that business object with some new business logic that has these facilities that's right mr demon wolf you've you've spot on here um everything is fine being on fire is normal well if it's a barbecue yes you're you're doing just fine barbecue it should be on fire but if we're in a hospital not the case so we've now enhanced our our logger manager by introducing an eye logger formatter so we can pass in different features through that interface the next principle is the l in solid the list off substitution principle this one and it's named for i forget who list golf is but objects in the program should be replaceable with instances of their subtypes without altering the correctness of that program this means your object that's referencing a base class shouldn't have to know anything about the child class in order to interact with that base class you should be able to swap those objects out interacting with them inside of in this case my logger manager class without having to actually change the code in my logger manager [Music] so we don't have to rewrite the logger manager if we want to change it from logging to files to logging to that restful endpoint let's dig in a little bit further here so if we want to sync instead of to a file right we call these syncs right you instead of syncing to a file if we want to sync to somewhere on a web server to a restful service we can define an abstract class a a log sync that knows how to write messages it doesn't know how to do anything else just knows how to write messages and we can start to re-implement our file logger with a log sync implementing that as a as an abstract class that knows it knows how to create a new file it's not part of that implementation it knows how to write a message and it knows how to dispose and clean up that file and my logger manager down here now is going to specify that it has a log sync that is a new file logger we could change that in the fuse future so it's a new restful logger that implements log sync and consequently down here when we log our message logger right message it knows how to interact with that not bad but we haven't fully implemented the and we're still we've we haven't fully implemented the list off substitution principle here and we still have our our logger manager needing to understand about about the file about what the file name is and how to manage that file and you you see it here in the constructor it still has to know what these things are so we need to do a little bit more refactoring to to push these around a little bit and and in fact you see here i've got a couple of errors that we need to correct so i think we should take some time here and let's let's correct these errors to fully implement the l the liskov substitution principle um ah look at that uh looks like rick did some research here i'm assuming this is correct um the l the liskov is for barbara liskoff was one of the first women to be granted a doctorate in computer science in the united states it is and is a touring award winner who developed the liskov substitution principle nice thank you for looking that up rick um i'll have to i'll have to add a note here about um let's let's add a note add note about well done thank you uh barbara let's golf and i will make sure that is in the finished version of the of the notebook much appreciated rick um the substitution in the headline is missing a substitute you're right look at that pair programming at its best substitution there we go thank you so much um so we haven't fully implemented this properly right we still have we still have that some of the references to the file object lurking here that will correct well we need to correct [Music] so we want to implement the same interfaces we want to implement and provide that same facade of a log sync so these things here where we name new log file create new file and write message i'm sorry these two they need to go away and so does the file name manager because we're tightly coupled to the log sync being a file operation so instead of them occurring here in the logger manager let's move them up into the file logger so they occur there nothing says they must occur in here we'll move them over there so that all the interactions with files occur inside the file logger so i'll make that change now i'm going to copy these and for the purposes of of continuity so that folks that are watching the recording understand i'm going to comment them out so that they're not referenced here i'll go up to the file logger and i will paste these in and we're going to want to make these as part of a a constructor here so i will create a public file logger constructor there we go um and i'm not going to create a file name here i've already got a file name field available to me so i'll just reuse that and create new file when the file logger is created now realistically you don't want to create a file every time you interact with the file logger this has been simplified for the purposes of a sample you probably only want to create a new file when when it's time to create a new file you'd want some logic that determines when it's appropriate to create a new file and if it's not appropriate to create a new file point to the existing log file on disk and interact with that appropriately so um just some things to be aware of as we interact with this so continuing here we still have um this one file name manager this one we need to get rid of as well so i'll comment that out and go back up here to the file logger and paste that in so it has the same facade and you know what i think log sync should implement eye disposable so that we're sure that every log sync is disposable so um we know that classes can implement um interfaces including abstract classes so i'm just going to move this eye disposable implementation up here that way we're assured that our logger here is still supporting the dispose method here so we just moved a couple of those features that we had inside logger manager the rest of our public api for the logger manager is the same and if we try and run the code now hmm log sync does not implement dispose ah so i still need to go up here the abstract class needs to implement dispose so i'm going to create a public virtual void dispose method and i'll be able to swing down here and inside of the file logger i'll override that and now how do i look do i have some executing code the name underscore logger does not exist in the current context where's that line 17 let's go fix that one now so we're as you can see we're getting a lot more code and that's okay um here we go this is actually this object so i can delete that and now there we go now i've completed my interactions and it it's now behaving the same way and that log sync references the file logger and has the exact same api regardless of how we how we implement it so if we implement it to be able to surface and return restful interactions or maybe we want to log to a database or wherever send to a log queue you can you can still do those interactions and substitute that other log sync appropriately for interaction here okay so very interesting ways that you can consider implementing and substituting different objects and passing them around and changing the behavior of your application [Music] so um eagle hanson with a comment here in the chat room very good point an important thing about the list golf substitution principle is that in addition to the public surface of the type so if i go back up here to the log sync and it has those two methods not only those methods should be the same but their non-functional behavior should also be the same so the same exceptions should be raised so that they're still handled by the consuming class the same way they're they're still processed and passed on to other interactions appropriately good point there thank you so much eagle appreciate you chiming in with that so how we doing here any i i'm looking at some comments here in the chat room and um before i move on to the eye in solid um let me see here this is from buna i like the idea of using a game manager loot manager in game programming and how to use this experience in business applications for example a notification manager an authentication manager and so on exactly when right in game programming you might have a game manager that manages the interaction of the entire game you have a loop manager that manages the loot that you're finding in game in a business object you're going to have authentication manager that you're going to be able to compose and interact with inside of your application right and you're going to have database database manager you'll hear folks refer to as a repository and the repository pattern but you'll have these different classes these different implementations that are able to handle those various responsibilities very good point boona thank you for sharing thank you so much leila appreciate you tuning in um and uh thank you jason appreciate appreciate you uh you're chiming in there um priyanka asks can we have before and after code for liskoff in the notebook for comparison so i've just commented them out so um right i've the the changes are here and there that have been commented out and um right moving these objects in here so i will put a note in the comments here um so file name was there um this field and the constructor were added to complete complete the lsp implementation [Music] there you go so you can see that appropriately in the notebook i hope that helps priyank let me know all right let's let's get into the i now so i is the one that that we'll find folks get carried away with particularly in in c-sharp and dot-net circles you don't see this too much in in other object-oriented programming languages but when we talk about interfaces dot-net developers love to create interfaces and this is the interface segregation principle many client-specific interfaces are better than one general-purpose interface so the idea here is to create these many many many little utility interfaces that do one or two things very very well that way you only bring in those classes those objects you only load those into memory just those things that are doing the tasks that you are seeking out and you want to interact with right so you don't need to load massive huge objects right think about our big our big logger object that we had up here that knew how to interact and do all these interactions with the files this potentially could be a very large object with all of these interactions laid out inside of it but in the in the eye in the interface segregation we're going to split these out so that we have separate interactions separate capabilities for each one of these things that we might be doing now it is possible to create too many interfaces you're gonna just have to feel out when that happens if you quite literally have one class that implements one interface and throughout your entire application you just have class interface class interface class interface and it's one to one throughout your entire application you've probably implemented too many interfaces if you have more interfaces than you have classes in your application you've probably implemented too many interfaces so be forewarned i can define a couple of interfaces here to make it a little bit clearer the various things that we need that the tasks that we need inside of our logger sample here i could have an interface for our logger formatter and we already defined that up higher a file name manager we could define that interface as well so that it's clear that this object just knows how to do this thing and we could then create a single object perhaps that knows how to handle all of those things for us right because if we're interacting with files and this is right maybe not default file format or maybe this is the the maybe this is the what did i do i clicked the wrong button get back in there where'd my browser go did i ask did i seriously did i close my browser i closed my browser are you kidding me no it's over here that's where it went um maybe i don't want to call this a default file format maybe i just want to make call this file handler because it knows how to do all the things for files so right it knows how to be a logger formatter it knows how to be a file name manager and format my log messages make new log file names appropriately for us and that's okay that that we have those implemented here so that we can pass those in and interact with those for for our logger object and i could define many other little interfaces that do these very basic simple things for us because it and and devin points this out quite nicely here as a rule of thumb in object-oriented programming composition composing together from these various interfaces bringing together so that they pro they build a larger object that provides more business value composition is better than inheritance where we we're inheriting from a base class and we end up with a large inheritance hierarchy implementing interfaces implementing many interfaces is going to give you a lot more control over the growth of your application all right so there's more that i could do here but i think that's a good start for us moving along the next principle that we want to talk about is the dependency inversion principle this kind of kind of makes sense we've been talking about dependencies right that's what we're going to get into here in the last bit is dependency injection but the dependency inversion principle now that we've defined some base classes and some interfaces depend on those abstractions not concretions don't depend on concrete implementations of things this way it becomes easier to swap implementations of things think about it format log message as an interface here i can swap that out with a new implementation but that is maybe it's filehandlerv2 that has our new implementation of that and i can pass in a new version that does the same thing depend on that i logger formatter or that i file name manager and we can swap those appropriately pass in a new implementation when it's completed test it interact with it make sure that that new class does the thing that we want it to do and interact with it appropriately sorry we're not going to sorry deflex we're not going to answer questions about blazer during today's session that we can come back to either on my discord server or we can handle those questions tomorrow during my stream over on my channel so if we have tight coupling where we reference and we we create instances of other classes inside of our class now now we're going to end up with a a problem where we're tightly coupled to that we require changes to class b be reflected in class a but if we go through an abstraction through an interface through an abstract class definition now we end up with something that's a little bit more loosely coupled because we're referring just to that abstraction just to that interface change the implementation behind the interface go ahead and make whatever changes whatever testing you want to do the interaction from our class stays the same so here's our original logger how could we re-implement this so it only depends on the interfaces and abstract classes that we defined previously let's write a little bit of code here let's take a little bit of what we wrote bring it together and re-implement this so that it only references those interfaces before i get in and we start that exercise um i see a comment and a question here um and this is a a good question that we're i i touched on briefly at what point does di does dependency injection or dependency inversion become over engineered so i touch briefly on this if if you have if everything is one to one if you have one concrete class and it's the only class that implements an interface and you effectively have class interface class interface class interface throughout your application throughout your code you're a little over engineered if you have more interfaces and abstract classes then you do concrete classes you're a little over engineered um you want there to be a few classes that implement the same interface that implement the same abstract class so that you're able to you do that swapping if you don't need to swap those references if you don't have that extension point that you're going to need to cross for maintenance reasons in the future and we have the requirement that was defined here that we're going we're going to be looking at introducing interfaces and abstract classes doesn't create technical debt you can still couple directly to the initial classes that you are working with but now when you refactor to introduce those interfaces and abstract classes you're now opening yourself up right open close principle you're opening yourself up for modification so that those new features can be brought into your system and enhance it which will then only mean good things going forward when feature three becomes introduced feature four and you're able to reference those interfaces change and interact with those appropriately so [Music] we'll get in and um eagle that's i i get into that in the next sample so let's take this original logger i'm going to right i just copied down that original source code that we had and let's introduce another block here and i'm going to copy in what we had for this block that had the log sync the file logger uh logger file name manager etc so actually i could have let's copy this so there's cool hotkeys inside of um inside of jupyter notebooks um yeah see new is glue there's see that the next topic it's right there i can press v and it'll paste the section just below so here's log sync file logger implements that abstract log sync object um logger file name manager and i had suggested we have an interface up here right um i file name manager so let's bring this in and i already had logger formatter so i'm going to grab this interface bring that in um and here i file name manager and it implements name new log file right um i have my logger formatter with my normal logger formatter and we had implemented the the exception logger formatter i'm not going to reference that here and i'm going to get rid of this get rid of these for now and to help with that composing that's going on um and actually where was the logger file name manager here is referenced here i'm going to change this so that it's not referencing logger file name manager it's referencing i file name manager um and let's see the other one we had the logger formatter is being used down here so i think i think i'm okay i think i'm only referencing yep i'm referencing log sync i'm receiving my logger formatter to do logger format mess format the log message so that's great i'm only referencing right those those abstract classes and interfaces but i need to use those dependencies i want them to be made available so that it's it's easier for me to specify these things because the right i can i'm going to remove these comments because they're no longer needed here um right and let's just add a note here um now referencing the interface in uh as a type instead of the file name manager class right we're still coupled to it here because we're we're specifying it right there um right and i file name manager i already took care of that um referencing the log sync abstract class so that we can change our uh abstract interaction with log sinks later okay so and referencing the i logger formatter so that we can inject the desired format [Music] desired formatter right so we're passing these dependencies they're already defined they're already interacting and we're not write our interactions to work with these objects our our logger our formatter are only using that interface only using that abstract class right similarly up here only using that file name manager interface and when i when i execute this code it still works and outputs appropriately but i'm directly coupled to those objects i'm i'm passing a new object here i'm right i'm creating up here a new logger file name manager and this feeds into the next right so we've this is solid right we've we're depending on those abstractions not the concrete interactions as we go through later on in our class so realistically we don't want to create those objects and this is where the concept of dependency injection comes in and and this phrase new is glue we've separated the the references and how we use these objects so we're only referencing interfaces and and abstract classes but we're we're newing these up we're still glued to those types if if something happens and normal logger formatter starts to take a a constructor argument here well when i update normal logger formatter and i introduce that that argument here now i need to go and update my logger manager to be able to interact with it we don't want to do that that new statement is glue it glues these two classes together these two files if you made a change in the normal logger formatter you have to change your logger manager if i made a change to file logger and how it's created with that new statement here i need to change logger manager new is glue so if we want to loosely couple these if we want to really get to loosely coupling we should pass those objects in we should inject the implementations of log sync of i logger formatter so that that that external consumer of our logger manager is what's controlling these okay hence the term inversion of control we're injecting the concrete implementations of our dependencies it's not being controlled by our logger manager class but instead control has been inverted it's being passed in from outside so there's two different ways you can do this and and you can well there's several different ways you can do this um you can pass things in through the constructor of your class these are dependencies that are required in order to use your class so in this way you the only way to construct your class is if the dependency has been provided that's important you can't access you can't interact with this without providing that formatter that file sync that knows where it's going to go and save those files you can also use property injects injection specify a property and that property must be set with here's the thing that you need in order to use this class now folks like to sometimes use properties to allow optional dependencies to be set this is a way maybe you have a default implementation i had that normal formatter perhaps you have that still you want to have uh lingering around and you can have a property where you'll set anything that you want further detailed you want to implement a further more descriptive interaction of how you want things formatted you can use property injection method injection is what we were using inside of our format message interaction where we were passing in a class that had that business logic to implement the open close principle we were passing that in so we were injecting the object that we need only in the method that needs it this way that object isn't lingering around and and floating for folks to potentially and inadvertently interact with it when they shouldn't be hold off on talking about ios about containers there friends let's hold off let me i see a lot of discussion about this let's get there we'll we'll get there in about about object lifetime i'm going to pin that question hold that thought i see a lot of folks that are really excited in chat about this topic we're going to get there i promise so digging in right so i have this original logger and in fact let me get rid of this original logger i don't i don't need this source code here let's get rid of this one and i'm going to copy down that implementation we just finished okay and here it is so with our log sync our file name manager here's our file logger and and you see right that file name manager we need one of those in order to interact we're defaulting it to a new logger file name manager so we should really be passing that in from outside of our object so that this object doesn't necessarily know what a file name manager does what what the logger file name manager has that it's interacting with um so if we do that with the constructor here because this requires a file name manager it's kind of important i will specify and and this is probably one of the most popular ways that folks do dependency injection is through constructor it's very easy for the for you to then see everywhere that something's being injected um so i'll just call this file name manager and right i can get rid of that implementation and i can say uh let's see file name manager equals file name manager i'm stashing a copy of it in the private variable so i can go reference it later i am using it right away here to name the new log file and create a new file like i said this is a this is a basic sample it's intended to be overly simple so these operations might occur in in your log file manager somewhere else entirely and for the purposes of this we're just trying to make it real simple to connect the dots of where these things that reference and interact with each other so i put them next to each other now when that file logger is being referenced right down here inside of my logger manager well i can't just new create one of those and in fact i don't want to just new create one of those here i want to receive just a log sync and pass it along so once again i want to pass in i need a log sync for my logger manager to know how how and where to log its messages so i'm going to provide that constructor argument and i'm going to set logger equals whatever that sync is and now i remove that new statement now my logger needs to be provided from outside and this new logger manager doesn't know what to do with this so i can now inject those dependencies by specifying that i'm going to pass in a new file logger right so let's do that and i can't just pass in a new file logger i need to pass in a file name manager well a file name manager is one of these so i'll pass in one of those and that looks a little that looks a little unwieldy right if i execute the code it works it all behaves and i've now got these right these abstractions that are being referenced inside of logger manager logger manager doesn't know what file logger and what log file name manager are it knows only how to sync and how to how to write and how to format messages how to pass that along so we now have ways that we can change that behavior by passing in objects and i can certainly refactor this and turn these into into right var objects out here right if i have new uh right if i uh write if i call this file sync equals and i could do this right and further simplify right so i'm passing that in well right i need to i can also break this out and call this file name manager right and var file name manager equals that and that's nice that that looks a little bit easier for me to process as a as a human reading this but now now somewhere else in our code we've got all of these new statements that make it a little bit tricky a little bit right i need to know and wire these up appropriately so i'm either going to have some sort of a catalog class that defines here's here's what these things are and how to reference them and how to build them and right how how they relate to each other or well and gosh i'm gonna start to worry about scope i could get into memory leak here because right when did these things need to be cleaned up when when do they need to be released maybe they need to stick around for the lifetime priyanka actually asked this question a minute ago and i i parked it here how do you how do you control that object lifetime my file manager my file logger i probably want to close in and recreate when we have new files that we want to write where my logger manager i want to keep that logger manager around all the time so that i can always write log files and log messages to somewhere on disk or out on a web service so it can be consumed by by my support staff by my support team that's important so we start to get into questions about lifetime of these because if we if we're creating new ones of these and they're sticking around for a long time that's a memory leak you're creating objects and you're not cleaning them up afterwards so how do we do that how do we handle that and this is where the concept of an inversion of control or a dependency injection container comes in and you'll see interactions with containers particularly it comes by default with asp.net i'm sorry asp.net core with blazer you'll see these being used to define here's an interface and by default whenever that interface is requested we need to provide a concrete type not docker containers no docker containers something different [Music] well played um but this is a this is a container that contains your mappings from interface or abstract class to the concrete class that you would prefer to be passed in and it will specify and provide that interaction appropriately now there's many many different inversion of control containers out there dependency injection containers you'll see both names used for this this design pattern for this utility object there's a very very simple one that comes by default with asp.net core it's also used as part of blazer because blazer is part of asb net core um but there's literally dozens of them out there that folks have written the folks have used and it and it can be as simple as defining an object that specifies what this mapping is between objects so if we were to just mock up a class that does that for us watch us fritz is going to code this on the floor right let's call this the fritz container nothing bad ever ever came of writing code live on stream you know what i mean um auto fact structure map ninja prism yeah yeah there's tons of them out there um castle windsor um there there's a bunch so if i specify a container um i'm probably gonna have some mapping that lives out there so uh i'm gonna specify this as a private static read-only dictionary and it's to receive two types and this is our our mapping and i'll create this as a new uh new dictionary from this type to that type right i'm not going to put anything complex in here to do these these references these interactions um so you would have specified and we'll see this in the asp.net source code i'll show you that in a second you would have specified some sort of a register interface right that's going to specify a type and another type right and one type implements the other and actually we want to have this something like this where it's an interface and it's a type and what you would end up doing is saying mapping dot at something like register mapping something like that is the format that you'll typically see um so you would say mapping add and it would be uh the type of the interface to the type of that type and at some point you would have um you would have some sort of a method called resolve requesting an object of some type right and there's other ways that folks do this where they pass in different scopes different instances of objects and in this very simple implementation um we would return right some sort of a um and actually this was going to have there's going to be a where in here where t is new because you want to be able to create a t um right and you would say something like this and it's not just like that it would inspect the constructor and figure out exactly how to create that type right and actually we want to um it wouldn't just say new t here would be new um mapping of whatever that type is um like that right which is kind of weird it's going to get that type out and we're going to go and create it and i don't want to get down into the minutia of reflection but at its simplest this is what it's doing now we could dig in and and go into how this is implemented there's all kinds of strategies for how this is implemented but we can look further into asp.net core and see exactly how these interactions occur inside of that very simple interface um i also need where t i uh you're right right it's gonna be something like that where t implements i i forget exactly if i have that comma correct there might be okay so if i were to go and show you now a basic asp.net core application so that we can see exactly how this is implemented over there um let me see let's create a new folder here i'm going to go into uh let's call this uh asp.net sample and i'm going to create a new a new asp.net core application here just so we can see what that register method looks like and i'm going to create let's create a web app so net new web app and we'll get into command line interface as we get into the actual implementation of an application format here and that'll probably be with net5 in november so inside of asp.net core we have a startup class and we implement our dependency injection here inside of this configure services method and this is saying services ad razor pages this says add the razor pages service so that whenever something requests to interact with razer pages the things that format our application it's going to resolve and make that available for you we can also do things like say ah rats my tabs aren't going to line up um services add transient right and i would specify some interface right i file name manager uh normal file name manager and what this does is it specifies just like i i showed you over here right it's registering a mapping it's creating a mapping from an interface to a type so that whenever a file name manager in i file name manager is requested the normal file name manager will be constructed and provided for you so what these things can then be requested by by creating classes that have constructors that request in i'll i file name manager interface now scope and and making sure that we only create that we only create those objects when we need them we reclaim them when we don't need them that scope of how long that object lives is defined by this add transient statement there's three different scopes that you'll see used inside of the asp.net core service locator this this dependency object this dependency injection container not only is there add transient but there's also a scope called scoped and there's finally one called singleton transient means every time that i request an ifile name manager this object is created for me a singleton says that when this object is requested i file name manager one and only one normal file name manager is created for this instance of the application and is managed for the entire lifetime of the asp.net core application it will never be destroyed and it will never be recreated by the service locator by the container that's important because if you put things into there that it should be destroying and recreating it's not destroying and recreating those it's hanging on to them and will linger and you may inadvertently be creating a memory leak now i skipped the one in the middle a scope called scoped that's weird fritz what are you doing to me here in this case when we think about processing and producing a web page right and that's what asp.net is it produces web pages in asp.net core when we produce a web page there are times where we want to create an object only for the duration of when we're rendering that webpage great example of this is interaction with a database if you're interacting with the database and it's specific and we want to make sure that it's secured and only available to the interactions that that user that visitor to your website is doing from the point that we began processing the web page or the api that they're interacting with to the end of that and we finally finished delivering all the content to that visitor that is the scope of a request so add scoped says create one file name manager in this case only one ifile name manager a normal file name manager and if any other classes any other objects during this one web page request this one api request ask to interact with the file name manager give it the same one this means that when i interact with the service fritz gets his own version of file name manager when jason i see jason there in chat when jason makes a request to the service jason is handed jason's instance of the file name manager different it's a different instance than the one that jeff is working with that fritz is working with over here okay so there aren't any chances for those two objects to collide in memory for me to inadvertently access and interact with anything that jason is interacting with because they're kept separate and isolated you typically see this used for database interaction you see this for authentication interactions so that your authentication your interactions with your roles and your security claims those things that are security significant to grant you access to an application or to grant you access to the appropriate data you're interacting with in the data store are maintained separately from other users that's where you want to see that scoped interaction happen marcus that's a that's another great um example there that we should highlight you don't want to share a shopping basket with another user on a website absolutely right mrs c sharp fritz is out there doing holiday shopping and i go and log on from my machine and we're on the same network it shouldn't inadvertently show me mrs c sharp fritz's shopping basket because now i'm going to see what i'm getting for holiday hey that's while that might be fun for me to see for you as an application operator ah you've just violated your your customers trust so those are examples of the three different scopes that come with the default dependency injection container the the service locator is the pattern that it's implementing that you see inside of asp.net core and yes i'm doing this with no intellisense and i'm purposely doing it in notepad because i don't want to bring in and get into things with visual studio or visual studio code yet okay i think we do need to get into entity framework in the next in the next episode but we've gone through a lot here we've we really discussed a lot about how you can interact with how you can write code that adheres to these principles and these are principles they're not laws they're really more of a recommendation they're kind of a guideline um and you you you cannot uh request a bargain parlay like a pirate of the caribbean here but they're they're principles that if you if you follow them they'll help make your code better more maintainable and perhaps you'll even find you'll find some places where you can optimize for performance because you've started to isolate the responsibilities of your application you've started to place things discreetly into different locations so that it's easier to maintain source control will be easier to maintain because you've broken out a very complex object so that it's now residing and is is being composed of all the various child responsibilities that could be interacting and managing that object it's important that you understand that this isn't required but you're going to be able to do much better things when you do implement and you pursue design patterns design principles like the solid principles dependency injection isn't required and in fact some of our friends that work on mobile apps will tell you dependency injection and implementing many different interfaces for a mobile application ends up creating more code and more code management than what you really need where on a mobile device we want as small a code base as possible and in that case creating one object that knows how to do all the things that's appropriate for that mobile device may in fact be more appropriate for that solution versus in a desktop application a web application in iot application you may want to have these or cloud service you may want to have this broken out into more discrete objects that you can deploy appropriately certainly if you're deploying some of these objects to an iot device you want those to be as small as possible and implementing those very discreet interfaces so you only deploy those things will be beneficial devin you're spot on in implementing these principles you you must at some point visit one of the next tenets we need to get into testing testing testing and the more that you test your code the more assurances you'll have that each of these individual components that we've now broken things out into are going to work appropriately um you're right jason inside of asp.net core let me let me highlight this um inside of asp.net core there are some helper methods around that services object to help register interactions with database to help register interactions with web servers using what's called the http client there are shortcut methods that will register and configure that for you and we can get into that more when we start down the asp.net core um application series that i think will end up starting probably late november is what we'll be looking at because we're gonna end up with a week off for dot net conf we'll have a week in there where we'll do some c-sharp nine discussion um and i think we wanna drop in a unit testing discussion at the beginning of the month as well so actually no next week will be next week is the beginning of the month so we'll do database next week we'll probably do unit testing after that c sharp nine and we'll get into asp.net core testing factory no gosh no um and um what about well what about using statements versus dependency injection i would lean towards dependency injection and and pass that control out outside of your object so that it's being it's being handled appropriately you can create your objects and use a using statement to populate how populate those dependencies that works as well you don't have to use the dependency injection container that service locator object you don't have to use that you can use using statements to compose that appropriately and and take direct control over how that interaction happens but you don't need to do that taking a look here i think we're about done then we're done a little bit early today that's all right so let me head over let's uh head back over to our our main display this one i want to be over here um and we've covered a lot these are these principles are not something that you're going to learn overnight they're not something that you're going to be an expert at immediately but there's something that you can practice you can explore test out a little bit and that's why i it was important to me to use jupyter notebooks for this tinker with some of that source code add some new features in there see how it ripples through the rest of the architecture for that logger object that we created break things you saw what happened when i put in some errors you get a little error message that happens below no big deal go ahead and do that and and figure out what what patterns which of these principles makes sense to you things that maybe you haven't tried before take a look and see if it if it starts to make sense dependency injection isn't required to work with asp.net core it's something that will help make your interactions easier but take a look at at what's involved there take a look at how i wrote some of that code where we're injecting the objects in by hand and what that means for your application um and i think that's all the time we have for today i'm uh i'm gonna wrap it up right there make sure yeah there it is and i think we're gonna call it the end of a stream thank you so much friends i really appreciate you tuning in if you're watching the youtube recording check out the entire playlist of all things in the c-sharp with c-sharp fritz um catalog it's you're gonna see a little button that appears right about here that'll take you back to our previous episode episode eight and there'll be another button up here somewhere that'll take you to the entire playlist so you can click through and watch all the videos um for those of you watching on twitch in a few hours this video will be available over there and i hope you come out back around next week next monday 9 a.m eastern 6 a.m pacific tune in before you go out for your morning jog or even listen to me during your morning jog there on the west coast or 1300 utc finish lunch and tune in and join us over here and we're going to talk next week about database with entity framework and how you can interact with the database using the the simple entity framework object relational mapper tools and i'm going to have a blog post in just a few days that shows the schedule of all the sessions coming up and with links to the rest of the uh the playlist for you thank you so much friends i really appreciate you tuning in and i'll see you next time
Info
Channel: dotNET
Views: 10,339
Rating: undefined out of 5
Keywords: .NET, c#, c sharp, csharp, training, software developer
Id: 2RKF8XTf0Fc
Channel Id: undefined
Length: 100min 7sec (6007 seconds)
Published: Tue Oct 27 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.