Very nice video again! :-) Dependency injection is covered very well, but the dependency inversion was for me a bit incomplete. As I have seen you have just implemented a "strategy pattern". In my opinion dependency inversion is a bit more than that - though the machinery is the same. The essence of dep. inversion: We want to make that the peripheral details depend on our precious business logic and not the way around. In you example I see the Order and Payment Processor as the business logic, which we want to protect from peripheral details. Therefore we split the Order and Payment Processor into a separate module and there - beside the Payment process implementation - we define the Authorizer interface (ABC in python terms). So the business logic component is "virtualized", it runs on an abstract peripheral - as we have only an interface for the peripheral. Then in a Authorizer module we implement the Authorizer interface from the business logic module. If we do so, then we have flipped (inverted) the dependency. The business logic (Order, Payment Processor, abstract Authorizer interface) does not depend on the concrete peripheral implementation any more, it depends only on its internal interface (Autohrizer interface). On the other hand the Authorizer (peripheral) depends on the business logic as it implements the Authorizer interface. So just by shifting the interface from the peripheral component to he business logic component we have flipped (inverted) the direction of the dependency between the components. I think it is hard to demonstrate it well if all the logic is in one file - you do not see the component (module) boundaries. It would easier if you split up the code: the business logic component (python module) would contain the Order,Payment Processor, Authorizer interface and the peripheral component would contain the Authorizer implementation classes. So the "import" statements would reveal the real power of dep. inversion. You would have an "from business_logic import Authorizer" in the peripheral code, but in the business logic you would have no "from Authorizers import SMS_Authorizer" statement. Another improvement could be that the composition root is also in another component (this is what you have now in the main funcion). The composition root would wire everything together so, it would be dependent on everything (business logic and authorizer peripherals).
Thanks for your feedback! It's true that to get "real" dependency inversion, you'd split out the different classes into different files. I have not done that for these examples to keep things simple, but in this case it might have been helpful to do that. I'm trying to ride a careful balance between having practical examples that actually show how it works in "the real world" but still keeping things simple enough that my videos about them make sense :).
Thanks, Tamas. Your comment was polite and constructive! I am actually gaining twice the value: from the videos and the amazing constructive comments, such as this one! The idea of the importing statements made a lot of sense to me regarding the “inversion” part! Cheers!
Great video and great comment, just to get things straight (correct me if I'm wrong): Dependency Inversion is injecting generic abstract dependencies rather than the actual dependencies. This makes the actual implementation of a said dependency irrelevant to the dependent class therefor making looser coupled code.
@@magenertech9412 What you describe is rather the dependency injection: where you want to consume a class, you consume an interface instead and the instance is given/injected to your class. For example if you want logging in your library, then you expect in your constructor arguments a logger interface. Then when the system starts-up it can decide what logging system will be injected. For your this detail is irrelevant, you just use the interface. The dependency inversion is a bit different topic: It helps you to place the interfaces (like the logging interface) into libraries. In the classical approach you have a logging library, which has a logger interface. If you want to use logging via that interface, then you become dependent on that specific logging library. So basically your flexibility is reduced to a certain level, that you do not have to know which exact logger from the logging library is injected into your code (say: network logger, file logger, console logger), but you are limited only to those loggers, which are coming from that specific logging library. If you do a step forward and you want to get rid of this restriction, too, then you apply the dependency inversion principle. In this case you do not use an interface from the logging library but you declare and interface in your library yourselves. Then in the system code (say the main function) the user of your library creates a small adapter class, which maps between your logging interface and the interface of the logging library. This way you became absolutely independent of any logging framework, your library is much more versatile. And where is the inversion? Now your library is not dependent on the logging library, so you do not have outgoing dependency, but the adapter becomes dependent on your library. So you have flipped the direction of the dependency just by pulling the interface into your library.
I usually refer to databases when thinking about dependecies. Your project will likely be just fine if it uses mariadb or postgres or oracle or mssql.... Just provide an object that can run basic SQL queries, doesn't matter which.
Nice video. One important detail you could add for tests is that instead of instantiating an actual object we normally create a mock object which inherits the abstract class. The mock implementation obviously will have dummy return values. But that will highlight the true power of dependency injection in lieu of unit testing.
I've also encountered similar pain when running tests where my function creates an object within, and I need that object to be in a certain state in order for my test to pass. The thing I had to do (not really recommended, but it works), is you use mock on the "__init__" method of the class for the object you create. In your example here, you would have to mock the "__init__" method of the class Authorizer_SMS
Dependency inversion is the process of introducing a new layer between objects in the form of abstract class to reduce dependancy between objects directly and also means that these can be replaced with their subtypes. - This is hands down the most simple definition I have ever heard. Thank you so much.
@@DerekHohls In python certainly no. You can just duck type and just call the methods on the passed objects. As the method call is resolved dynamically at runtime, it works if you pass the right kind of object and most certainly fail if not. The only disadvantage of that approach is that it makes IDE/linting support harder, so less likely to have autocompletion or detection of completely wrongly passed objects before starting the program. (You can work that out by helping the linters with specific mypy or pylint or w/e header files for the objects or improving the IDEs. For several highly dynamic projects that uses dependency inversion/injection a lot like Django, that's indeed what's done there). And of course, what also can happen that for accident the passed object is the wrong one, but supports the called method, so your business logic is doing something completely wrong just because two different methods are called the same for different classes (this of cause concerns the purist, but is rarely a real problem). The main motivation for the abstract classes is coming from the static compiled languages that just need to know an interface to check it at compile time and prepare the calling in the compiled code already (internally they also reserve some at compile timed known locations like offsets where the address of the method is stored, so they can just say at assembly JUMP to that address). But even in those, big dependency inverse/injection systems are often implemented by (what is there called) reflection what is just dynamical lookup with a bit more type safetyness. The classical example for it would be Java Spring, but most other bigger Java systems use this kind of reflection a lot, too. The most negative example would be log4j where this resulted in a hazard security problem :-) - But indeed, the pattern can also be completely language agnostic. E.g. Kubernetes uses it also to a high degree and with just configuration in yaml files (again bypassing a very static language like Go with a very dynamic injection system). So, the abstract classes are the old school examples coming from old Java in the design patterns written in the 90s. They are still valid, as unless you want to write a big framework, in static languages, they are often enough choosen as path of least resistance and "most" control. But certainly, the dependency inversion/injection is not necessarily achieved by abstract classes and I guess (I don't have hard numbers on that), that most big system don't rely on it per se. (Usually they still have it, even though it's technical not necessary, but as a way to communicate the necessary interface and it's easier to write an abstract interface/class and have an auto documentation, auto linting, auto completion, etc.) - than to "just" write a good documentation and a good error handling if things are not done right :-)
Thank you! I watched a dozen videos on the topic, but this one not only explained it in the best way, but also felt like a way more complete explanation!
Fantastic Tutorial, you explain why something needs to be done instead of just explaining how it's done(which are most of the tutorials in UA-cam). And it's very easy to retain the knowledge because now you know why's behind the logic. Great job !!
Keep up the good work Arjan. These Python OOP topics are helping me understand a lot of new concepts! I also like how you demonstrated unittests and code coverage!
Thank you for having the videos in this series build on concepts of earlier videos. I have been binging your videos, and now while following your example of dependency inversion, I could already tell how you were going to apply that concept and the code you'd write. Thank you Arjan!
6:24 I'm glad that I'm not the only one having problems with this mock system. I had such huge problems with this, that I was forced to use dependency injection. Thank you python standard library to make me a better person!
@@felipealvarez1982 Thanks! I find mocking quite an obscure topic to understand; but Arjan's use of it here made perfect sense. Hope he tackles the topic in depth at some point.
your test cases with repeat initialization of sms authorizer are a great starting point for a pytest fixtures tutorial, which are a powerful tool as well. Great video, as always!
Wow !! Just one video and I subscribed . The content and quality is outstanding !!!! thank you so much for sharing the knowledge in such a easy way. I look forward to more such videos.
This is a wonderfully clear explanation of a potentially very complex topic. Thank you very much. I tend to prefi my instance variables with _ (e.g. self.autborizer) so that mypi will warn if I call it from outside the class. This avoids accidentally introducing coupling through injected components.
Awesome video and very illustrative examples, love it! I noticed you were running Python 3.9 so I would have also consider to use Protocols instead of ABC, which was introduced in Python 3.8
Coding Away, one of your video came up on in the background... 3 hours later, i think you're one of the best presenters on UA-cam :) Very knowledgeable and not afraid of diving down deep. Thank you for all your time and effort! PS i totally hate the python module system of 3.7 (we have some legacy code and we cant get off the damn version) - are there notable improvements between 3.7 and say 3.9 or even 3.10?
OH! and if you even see the above comment I have to ask: are you able to target bytecode (WHL/Eggs?) written in 3.9/3.10 so we can write modules in newer versions and know they'll work on the older 3.7 codebase? i'm pretty new to Py - sorry if its a stupid question.
This is really good! But a subtle point: DI/IOC is possible not only with classes but in functions. The theory is the same, the issues and benefeits are the same as well :)
Thanks for sharing this high quality content. Very clear explanations. I'm learning Python coming from an embedded systems background. I need to pickup the language asap. I'm currently debating on whether to pay for.a course or teach myself with books, videos and doing projects. Would be great to know how you or others reading this have learned Python.
As ever, an excellent video but could I just recommend that watchers follow this up with Code Aesthetic's equally excellent Dependency Injection video.
Enjoyed the video. The patches are a smell for injection. It felt like you were going to inject input. Was that cut in the interest of time? It opens up so much extensibility.
so dependency injection is the way to pass a class and inversion is the interface / abstract so the passed class not tied to one specific class. noted, thank you mr arjan
As primarily a Rubyist, this feels like a halfway house to me between type checking and duck typing. I'm a big fan of duck typing wherever possible, and it seems like it would work perfectly well here - instead of creating the abstract class, I think you could just remove the assertion on PaymentProcessor that its authoriser has any particular class type? I realise that type checking is often valuable, but in cases such as this where you want something more flexible, why is dependency inversion better than just leaving the type fully open?
Hey thanks for your videos, they are great. I have a question for this one. Is there any reason you use things like is_authorized and set_status instead of using the property decorator?
When I published this video I was not yet fully committed to Python-only on my channel, so I wanted to keep the more Python-specific things such as decorators out of the examples for simplicity. I've now moved to fully focus on Python in my channel, so you can expect to see these kinds of things appear more frequently in my upcoming videos.
In your unit test you use a real authorizer instance rather than a spec'd mock. I've always used mocks to reduce coupling in tests, but that often convolutes the test and sometimes the mock behavior diverges over time. So I can see the merits of using the real instance. What are your thoughts?
I didn’t use a mock for the authorizer since it is a really basic class (not much more than a mock actually). If a class is relatively simple and is more or less standalone, I don’t have any issues using instances of it directly in the test. But then I wrok at a startup where we have to move fast sometimes, so that means making sacrifices once in a while.
Hey Arjan! Recently I've been diving into your videos, they're so amazing! Your knowledge is very SOLID and the references and arguments you use are very clarifying! Thank you for posting such amazing content! I started using python in 2011 and at that time people would frown when the subject was threads. Most of my peers would use Java saying the support for using threads was a lot better, do you think/plan on touching this subject? And/or even making some comments explaining if threads are helpful in the web development context or a microservices approach would be a better approach
Hi Paulo, thank you - I'm glad you're enjoying the videos! I must admit I haven't used threads in many years. When I need asynchronous behavior, I'll generally use asyncio/Futures. But I can imagine there are still quite a few types of applications where threads are important (I just don't happen to work on those :) ). Async is a topic that's on my video list, so I'll surely cover that on the channel at some point.
thanks for the video! instead of a docstring or a rc file, can't you also just raise notimoplementederror for coverage to ignore abstract classes? or decorate them as abstract?
Hey Arjan, would you recomment injecting and mocking the builtin "input" function as well? How do you decide when to inject and when to just directly use an object/function?
Great to see you solve the problem that you created by using the type system. It shows the cost of adding the type, but what is the marginal value (on top of unit tests)? The abstract class reduces the dependency to the interface the ABC defines, but if you'd not used types (and the interface was wrong) your test would just throw an attribute error that makes the issue just as clear. I honestly don't get it. Can you explain?
Hi David, the Python interpreter completely ignores type hints - they don't have any effect whatsoever on what the code does when you run it. The main reason types are there is to provide help to the developer. Thus, types don't create problems, they make them explicit, and allow using tools like Pylance and Mypy to help you avoid mistakes while you're writing the software. I think it's a bad idea to use failing unit tests for establishing what the interface is between pieces of code. Do you really want to sift through a bunch of unit tests to figure out what kind of objects with attributes should be passed to a function as opposed to having that information clearly defined by types (which can then be automatically checked)? Finally, if you write code in a team of developers (which is probably true for many developers working on commercial software), types help establish what the interface is, and that + automatic type checking makes adding new code or refactoring existing code faster for the developers in your team.
@@ArjanCodes thanks for the response. By problem I meant the coupling. After the pendacy is injected, there's no longer any dependacy on the class beyond the annotation. I see how something like mypy is useful if you're not writing tests, but I fall to see what it adds if you are. Is there an example of a type of issue MyPy would catch that a good unit test wouldn't?
@@davidlayton5054 "Is there an example of a type of issue MyPy would catch that a good unit test wouldn't?" Yes, it is true that if you commit to having 100% test coverage static typing won't catch any errors your tests wouldn't. However, why sign up for 100% coverage and the time that entails when you can use static typing while being sure that the case of "are the types correct" is getting caught at build time?
@@9e7exkbzvwpf7c Thank you so much for your reply. I definitely see the value in what you are saying. I do TDD b/c I program a lot faster that way-- not b/c I think it is the one-true way. So I'm personally already signed up to 100% coverage. I certainly see the value in tool like MyPy.
Is this abstract class or "intermediate layer" as you call it an interface? If not, what's the difference between such an abstract class and an interface?
OOP brings a whole other level to programming. Much more than watching simple C language tutorials. I can't imaging Oracle bringing these OOP concepts to PL/SQL...or GNU Scheme...? Didn't the industry switch to Data Oriented Programming?
Is there a reason the PaymentProcessor needs to take an authorizer argument at all instead of there being another process_order method on the Order class which authorizes the user and then takes payment? Is it because payment shouldn't be possible without authorization every time, so the strict usage of authorize inside pay method is necessary? Would refactoring authorize and pay to happen separately but inside a process_order method also be dependency inversion?
It looks like generate_sms() is not called as part of the new code. How will you retractor the codes so it is included when the class authorise_sms is used and excluded while authorise_robot is used?
I have a question. Composition is for example if a class Car has an attribute of another class ie Engine and if the outer and inner object have the same lifetime. Otherwise its an aggregate. So if each Car has the same engine by default(engine gets created in car) its not dependency injection only aggregation. If the engine is created outside of Car an is passed through init to assign to Car.engine it is both injection and aggregation. If Car has no attribute engine but uses the engine in one of its method that expects Engine it is only dependency injection. Is what I said correct?
hello, authorizer is abc and has common functions, but when I give the sms method to Payment Processor, and intellisense like authorizer.get_sms_code() does not appear, how can I provide this?
Is there a video where you explain the difference between a design "pattern" and "principle" ? It wouldn't shock me that dependency injection be called a "principle", same if dependency inversion was a "pattern". Is there a clear-cut difference between both or is it fuzzy ?
The way I view it is that a principle is more of a guideline, whereas a pattern is a specific solution to a design problem. In that sense, "dependency inversion" is a principle because you can apply it in many different ways, whereas the "strategy" is a design pattern, because it prescribes a specific organization of classes and methods. To draw a carpentry analogy: a design principle is "make your wooden construction such that it can support at least twice the weight of what's needed"; a design pattern is a dovetail joint.
Great video but I am a bit confused. You removed the authorizer.generate_sms_code() from the actual code but you used it in the test case. You said you would go back to it but I seem to miss it. Isn’t that very important in the actual code? Where is it going to be run?
If I understood this correctly an extra layer of abstraction is created between the Authorizer subclasses and the PaymentProcessor, where do we define which Authorizer is used? Am I missing something
I think I got it, you pass this on the test by setting each auth on the various authorizers, something that would be the case should the code is implemented
Thanks Pascal, that's just an image I created myself using a photo from Unsplash + added the two colors. If you send me an email at business@arjancodes.com, I'd be happy to share it with you.
Do you think the initializer test is actually needed? Aren't you testing something that belongs to the python codebase? Instances of classes should be of the expected type
test_init couples your test to the implementation. if you decide to refactor and rename the variable to _authoriser, the test will break. IMHO you should test the public interface of the class to allow free refactoring of your implementation later. Technically "authoriser" variable is public in this example but maybe it shouldn't?
@@ArjanCodes I understand that you want to simplify the examples to demonstrate something but at the same time there are people learning from your code and they may think that this is what they need to do. They don't know that you do not do it in production code. I really like your lessons but as a learning resource I think the code should be perfect and production ready.
This might just be a minor detail, but why did you choose "1234567" as the wrong auth code. Having to know that a valid auth code has to have exactly 6 digits seems like a hurdle for understanding. Why not use an auth code like "wrong1" or "invalid"?
2 роки тому
A suggestion: can you please increase the font size of your IDE a little bit. It's impossible for me to watch your videos on the phone.
Finally upgraded to better microphones for this video!
It makes a huge difference!
You could record an audiobook about python now haha!
The RE20 is an excellent choice. Love this channel
I'm only a couple of minutes in so far, but I'm actually finding it hard to listen because of the music...
It’s about time 😉
Very nice video again! :-) Dependency injection is covered very well, but the dependency inversion was for me a bit incomplete. As I have seen you have just implemented a "strategy pattern". In my opinion dependency inversion is a bit more than that - though the machinery is the same. The essence of dep. inversion: We want to make that the peripheral details depend on our precious business logic and not the way around. In you example I see the Order and Payment Processor as the business logic, which we want to protect from peripheral details. Therefore we split the Order and Payment Processor into a separate module and there - beside the Payment process implementation - we define the Authorizer interface (ABC in python terms). So the business logic component is "virtualized", it runs on an abstract peripheral - as we have only an interface for the peripheral. Then in a Authorizer module we implement the Authorizer interface from the business logic module. If we do so, then we have flipped (inverted) the dependency. The business logic (Order, Payment Processor, abstract Authorizer interface) does not depend on the concrete peripheral implementation any more, it depends only on its internal interface (Autohrizer interface). On the other hand the Authorizer (peripheral) depends on the business logic as it implements the Authorizer interface. So just by shifting the interface from the peripheral component to he business logic component we have flipped (inverted) the direction of the dependency between the components. I think it is hard to demonstrate it well if all the logic is in one file - you do not see the component (module) boundaries. It would easier if you split up the code: the business logic component (python module) would contain the Order,Payment Processor, Authorizer interface and the peripheral component would contain the Authorizer implementation classes. So the "import" statements would reveal the real power of dep. inversion. You would have an "from business_logic import Authorizer" in the peripheral code, but in the business logic you would have no "from Authorizers import SMS_Authorizer" statement. Another improvement could be that the composition root is also in another component (this is what you have now in the main funcion). The composition root would wire everything together so, it would be dependent on everything (business logic and authorizer peripherals).
Thanks for your feedback! It's true that to get "real" dependency inversion, you'd split out the different classes into different files. I have not done that for these examples to keep things simple, but in this case it might have been helpful to do that. I'm trying to ride a careful balance between having practical examples that actually show how it works in "the real world" but still keeping things simple enough that my videos about them make sense :).
Thanks, Tamas. Your comment was polite and constructive! I am actually gaining twice the value: from the videos and the amazing constructive comments, such as this one!
The idea of the importing statements made a lot of sense to me regarding the “inversion” part!
Cheers!
I was just asking myself "ok but why is it an 'inversion'?" And your example is crystal clear!
Thank you very much!
Great video and great comment, just to get things straight (correct me if I'm wrong):
Dependency Inversion is injecting generic abstract dependencies rather than the actual dependencies.
This makes the actual implementation of a said dependency irrelevant to the dependent class therefor making looser coupled code.
@@magenertech9412 What you describe is rather the dependency injection: where you want to consume a class, you consume an interface instead and the instance is given/injected to your class. For example if you want logging in your library, then you expect in your constructor arguments a logger interface. Then when the system starts-up it can decide what logging system will be injected. For your this detail is irrelevant, you just use the interface. The dependency inversion is a bit different topic: It helps you to place the interfaces (like the logging interface) into libraries. In the classical approach you have a logging library, which has a logger interface. If you want to use logging via that interface, then you become dependent on that specific logging library. So basically your flexibility is reduced to a certain level, that you do not have to know which exact logger from the logging library is injected into your code (say: network logger, file logger, console logger), but you are limited only to those loggers, which are coming from that specific logging library. If you do a step forward and you want to get rid of this restriction, too, then you apply the dependency inversion principle. In this case you do not use an interface from the logging library but you declare and interface in your library yourselves. Then in the system code (say the main function) the user of your library creates a small adapter class, which maps between your logging interface and the interface of the logging library. This way you became absolutely independent of any logging framework, your library is much more versatile. And where is the inversion? Now your library is not dependent on the logging library, so you do not have outgoing dependency, but the adapter becomes dependent on your library. So you have flipped the direction of the dependency just by pulling the interface into your library.
I like the background sayings
"It depends"
"Solid advice"
This is the first example I've ever seen that clearly shows the motivation behind an abstract class, great video!
Glad you liked it, Steven!
I usually refer to databases when thinking about dependecies. Your project will likely be just fine if it uses mariadb or postgres or oracle or mssql.... Just provide an object that can run basic SQL queries, doesn't matter which.
After learning about testing, there is more happiness and more smiles when all tests are pass than the actual code working properly 😂
Who cares about working code! 😄
If you write your tests properly, these two should be the same!
@@vekyll The testing can not prove that the code works properly, it can only prove that smth goes wrong )))
@@yuriysukhorukov391 Of course. This does not contradict what I said.
@@vekyll You can't fully test any non-trivial code. That's why for critical software there has to be analysis as well.
Nice video. One important detail you could add for tests is that instead of instantiating an actual object we normally create a mock object which inherits the abstract class. The mock implementation obviously will have dummy return values. But that will highlight the true power of dependency injection in lieu of unit testing.
Great suggestions, Thank you.
Finally clarified the differences between them, Thank You Arjan!
I've also encountered similar pain when running tests where my function creates an object within, and I need that object to be in a certain state in order for my test to pass. The thing I had to do (not really recommended, but it works), is you use mock on the "__init__" method of the class for the object you create. In your example here, you would have to mock the "__init__" method of the class Authorizer_SMS
Your videos are brilliant. Just love this content. I need to watch this video a couple times to catch everything going on.
Glad you enjoy it, Chuck!
Dependency inversion is the process of introducing a new layer between objects in the form of abstract class to reduce dependancy between objects directly and also means that these can be replaced with their subtypes. - This is hands down the most simple definition I have ever heard. Thank you so much.
Are ABC classes really the only way to achieve this?
@@DerekHohls In python certainly no. You can just duck type and just call the methods on the passed objects. As the method call is resolved dynamically at runtime, it works if you pass the right kind of object and most certainly fail if not. The only disadvantage of that approach is that it makes IDE/linting support harder, so less likely to have autocompletion or detection of completely wrongly passed objects before starting the program. (You can work that out by helping the linters with specific mypy or pylint or w/e header files for the objects or improving the IDEs. For several highly dynamic projects that uses dependency inversion/injection a lot like Django, that's indeed what's done there). And of course, what also can happen that for accident the passed object is the wrong one, but supports the called method, so your business logic is doing something completely wrong just because two different methods are called the same for different classes (this of cause concerns the purist, but is rarely a real problem).
The main motivation for the abstract classes is coming from the static compiled languages that just need to know an interface to check it at compile time and prepare the calling in the compiled code already (internally they also reserve some at compile timed known locations like offsets where the address of the method is stored, so they can just say at assembly JUMP to that address). But even in those, big dependency inverse/injection systems are often implemented by (what is there called) reflection what is just dynamical lookup with a bit more type safetyness. The classical example for it would be Java Spring, but most other bigger Java systems use this kind of reflection a lot, too. The most negative example would be log4j where this resulted in a hazard security problem :-) - But indeed, the pattern can also be completely language agnostic. E.g. Kubernetes uses it also to a high degree and with just configuration in yaml files (again bypassing a very static language like Go with a very dynamic injection system).
So, the abstract classes are the old school examples coming from old Java in the design patterns written in the 90s. They are still valid, as unless you want to write a big framework, in static languages, they are often enough choosen as path of least resistance and "most" control. But certainly, the dependency inversion/injection is not necessarily achieved by abstract classes and I guess (I don't have hard numbers on that), that most big system don't rely on it per se. (Usually they still have it, even though it's technical not necessary, but as a way to communicate the necessary interface and it's easier to write an abstract interface/class and have an auto documentation, auto linting, auto completion, etc.) - than to "just" write a good documentation and a good error handling if things are not done right :-)
Thank you! I watched a dozen videos on the topic, but this one not only explained it in the best way, but also felt like a way more complete explanation!
Thank you!
Fantastic Tutorial, you explain why something needs to be done instead of just explaining how it's done(which are most of the tutorials in UA-cam). And it's very easy to retain the knowledge because now you know why's behind the logic. Great job !!
Much appreciated! Thank you very much!
Keep up the good work Arjan. These Python OOP topics are helping me understand a lot of new concepts! I also like how you demonstrated unittests and code coverage!
Thank you Ali, and will do!
Well structured thorough video that includes design pattern and unit testing. Thank for sharing!
Thank you for having the videos in this series build on concepts of earlier videos. I have been binging your videos, and now while following your example of dependency inversion, I could already tell how you were going to apply that concept and the code you'd write. Thank you Arjan!
Finally I can see an example to understand DI clearly. Thank you Arjan for your explanation :)
My pleasure 😊
6:24 I'm glad that I'm not the only one having problems with this mock system. I had such huge problems with this, that I was forced to use dependency injection. Thank you python standard library to make me a better person!
I was delighted to see mock and testing! Every programmer should be doing this!
I missed seeing mock objects here - where were they used?
@@DerekHohls I think at 4:06 and 9:49
@@felipealvarez1982 Those snippets refer to "patch" not mock?
@@DerekHohls patch is a convenience method for mock. Same library.
@@felipealvarez1982 Thanks! I find mocking quite an obscure topic to understand; but Arjan's use of it here made perfect sense. Hope he tackles the topic in depth at some point.
your test cases with repeat initialization of sms authorizer are a great starting point for a pytest fixtures tutorial, which are a powerful tool as well. Great video, as always!
Thanks! And that's certainly something I'm going to cover soon on the channel!
Wow !! Just one video and I subscribed . The content and quality is outstanding !!!! thank you so much for sharing the knowledge in such a easy way. I look forward to more such videos.
Welcome aboard! ;)
I absolutely love this channel! Its pure joy to watch these tutorials! Thanks a lot!
Thanks so much, Andy!
So clear, so concise, so awesome! Thank you Arjan
You are the coolest developer ever
Thank you so much for the kind words!
superb.
what should be the guideline to splitting it properly?
Thank you for really clear and helpful explanation. Code examples are very illustrative too. Nice work!
Thank you - glad you like it!
Great video. Would be nice to use bigger font. Very hard to watch full screen on mobile phone.
This is a wonderfully clear explanation of a potentially very complex topic. Thank you very much. I tend to prefi my instance variables with _ (e.g. self.autborizer) so that mypi will warn if I call it from outside the class. This avoids accidentally introducing coupling through injected components.
Best video I have seen regarding DIs. Thanks!
Glad it was helpful, Dmytro!
Great video, more people should start teaching like you.
Thank you, happy you liked the video!
Best programming education videos out there.
Thank you so much - glad you like them!
Extremely clear explanation. Great! Thank you!
Glad you enjoyed it, Paul!
best tutorial for advance topics
I like the tests over the driver scripts of previous videos.
Awesome video and very illustrative examples, love it! I noticed you were running Python 3.9 so I would have also consider to use Protocols instead of ABC, which was introduced in Python 3.8
Thank you! I’m working on new examples at the moment that rely on Protocol instead of ABCs.
Excellent video. Thanks for making and sharing this.
Glad you enjoyed it!
Coding Away, one of your video came up on in the background... 3 hours later, i think you're one of the best presenters on UA-cam :) Very knowledgeable and not afraid of diving down deep. Thank you for all your time and effort! PS i totally hate the python module system of 3.7 (we have some legacy code and we cant get off the damn version) - are there notable improvements between 3.7 and say 3.9 or even 3.10?
OH! and if you even see the above comment I have to ask: are you able to target bytecode (WHL/Eggs?) written in 3.9/3.10 so we can write modules in newer versions and know they'll work on the older 3.7 codebase? i'm pretty new to Py - sorry if its a stupid question.
This is really good! But a subtle point: DI/IOC is possible not only with classes but in functions. The theory is the same, the issues and benefeits are the same as well :)
Are you talking about callbacks? How do you do DI in functions?
@@koraytugay By passing the dependencies as parameters. `def notify_user_signed_up(user: User, email_service: EmailService, jwt_service: JwtService)`
Thanks for sharing this high quality content. Very clear explanations. I'm learning Python coming from an embedded systems background. I need to pickup the language asap. I'm currently debating on whether to pay for.a course or teach myself with books, videos and doing projects. Would be great to know how you or others reading this have learned Python.
Awesome video, explanations, and examples! Thank you!
Thanks so much Brian, glad the content is helpful!
Turns out, I considered this common sense and used it in my code without even knowing what it is called.
As ever, an excellent video but could I just recommend that watchers follow this up with Code Aesthetic's equally excellent Dependency Injection video.
Enjoyed the video. The patches are a smell for injection. It felt like you were going to inject input. Was that cut in the interest of time? It opens up so much extensibility.
Ha, well spotted. I did cut a few things out to keep it from becoming too long.
so dependency injection is the way to pass a class and inversion is the interface / abstract so the passed class not tied to one specific class. noted, thank you mr arjan
Awesome explanation thank you took plenty of notes!
Glad you enjoyed it!
Not sure if your patch question was answered but you can patch __call__ of the class and return whatever you like.
As primarily a Rubyist, this feels like a halfway house to me between type checking and duck typing. I'm a big fan of duck typing wherever possible, and it seems like it would work perfectly well here - instead of creating the abstract class, I think you could just remove the assertion on PaymentProcessor that its authoriser has any particular class type?
I realise that type checking is often valuable, but in cases such as this where you want something more flexible, why is dependency inversion better than just leaving the type fully open?
Is there a blog post explaining dependency inversion in relevance to file structure?
super useful information about inversion and injection, thanks
Great CAPTCHA 10/10 would implement
Hey thanks for your videos, they are great. I have a question for this one. Is there any reason you use things like is_authorized and set_status instead of using the property decorator?
When I published this video I was not yet fully committed to Python-only on my channel, so I wanted to keep the more Python-specific things such as decorators out of the examples for simplicity. I've now moved to fully focus on Python in my channel, so you can expect to see these kinds of things appear more frequently in my upcoming videos.
@@ArjanCodes thank you! Great work 👌🏼
Dependency inversion is then something like the maxim "couple to interfaces, not implementations".
In your unit test you use a real authorizer instance rather than a spec'd mock. I've always used mocks to reduce coupling in tests, but that often convolutes the test and sometimes the mock behavior diverges over time. So I can see the merits of using the real instance. What are your thoughts?
I didn’t use a mock for the authorizer since it is a really basic class (not much more than a mock actually). If a class is relatively simple and is more or less standalone, I don’t have any issues using instances of it directly in the test. But then I wrok at a startup where we have to move fast sometimes, so that means making sacrifices once in a while.
Thanks for the video. Can you tell me what Color Theme do you use?
Hey Arjan! Recently I've been diving into your videos, they're so amazing!
Your knowledge is very SOLID and the references and arguments you use are very clarifying!
Thank you for posting such amazing content!
I started using python in 2011 and at that time people would frown when the subject was threads.
Most of my peers would use Java saying the support for using threads was a lot better, do you think/plan on touching this subject?
And/or even making some comments explaining if threads are helpful in the web development context or a microservices approach would be a better approach
Hi Paulo, thank you - I'm glad you're enjoying the videos! I must admit I haven't used threads in many years. When I need asynchronous behavior, I'll generally use asyncio/Futures. But I can imagine there are still quite a few types of applications where threads are important (I just don't happen to work on those :) ). Async is a topic that's on my video list, so I'll surely cover that on the channel at some point.
Thank you that is a nice video that goes directly in my favorites
Excellent content. Thanks a lot for making this!
Your content is amazing m8!
Thanks a ton, Lucas!
thanks for the video! instead of a docstring or a rc file, can't you also just raise notimoplementederror for coverage to ignore abstract classes? or decorate them as abstract?
Hey Arjan, would you recomment injecting and mocking the builtin "input" function as well? How do you decide when to inject and when to just directly use an object/function?
Hi @VikingTheDude, I'm going to release a video soon where I talk about unit testing and in particular patching and mocking. So stay tuned :).
Arjan, is it not more convenient to use pytest instead of unittest? Seems to be simpler and (maybe?) more flexible?
Great to see you solve the problem that you created by using the type system. It shows the cost of adding the type, but what is the marginal value (on top of unit tests)? The abstract class reduces the dependency to the interface the ABC defines, but if you'd not used types (and the interface was wrong) your test would just throw an attribute error that makes the issue just as clear. I honestly don't get it. Can you explain?
Hi David, the Python interpreter completely ignores type hints - they don't have any effect whatsoever on what the code does when you run it. The main reason types are there is to provide help to the developer. Thus, types don't create problems, they make them explicit, and allow using tools like Pylance and Mypy to help you avoid mistakes while you're writing the software.
I think it's a bad idea to use failing unit tests for establishing what the interface is between pieces of code. Do you really want to sift through a bunch of unit tests to figure out what kind of objects with attributes should be passed to a function as opposed to having that information clearly defined by types (which can then be automatically checked)?
Finally, if you write code in a team of developers (which is probably true for many developers working on commercial software), types help establish what the interface is, and that + automatic type checking makes adding new code or refactoring existing code faster for the developers in your team.
@@ArjanCodes thanks for the response. By problem I meant the coupling. After the pendacy is injected, there's no longer any dependacy on the class beyond the annotation.
I see how something like mypy is useful if you're not writing tests, but I fall to see what it adds if you are. Is there an example of a type of issue MyPy would catch that a good unit test wouldn't?
@@davidlayton5054 "Is there an example of a type of issue MyPy would catch that a good unit test wouldn't?" Yes, it is true that if you commit to having 100% test coverage static typing won't catch any errors your tests wouldn't. However, why sign up for 100% coverage and the time that entails when you can use static typing while being sure that the case of "are the types correct" is getting caught at build time?
@@9e7exkbzvwpf7c Thank you so much for your reply. I definitely see the value in what you are saying. I do TDD b/c I program a lot faster that way-- not b/c I think it is the one-true way. So I'm personally already signed up to 100% coverage. I certainly see the value in tool like MyPy.
I love your lighting!!
Thank you, John!
Is this abstract class or "intermediate layer" as you call it an interface? If not, what's the difference between such an abstract class and an interface?
When I saw the title i got instantly triggered by the "VS" just to find out that I love this video. So was it clickbait? Maybe, but i love it!
Glad you liked it!
Really nice videos I have learned a lot.
Thanks so much, Rusbel, glad it was helpful!
Amazing content! Creating a video about testing and mocking with pytest would be very interesting in my opinion.
Glad you like it Robert - and thank you for the suggestion!
OOP brings a whole other level to programming. Much more than watching simple C language tutorials. I can't imaging Oracle bringing these OOP concepts to PL/SQL...or GNU Scheme...? Didn't the industry switch to Data Oriented Programming?
Is there a reason the PaymentProcessor needs to take an authorizer argument at all instead of there being another process_order method on the Order class which authorizes the user and then takes payment? Is it because payment shouldn't be possible without authorization every time, so the strict usage of authorize inside pay method is necessary?
Would refactoring authorize and pay to happen separately but inside a process_order method also be dependency inversion?
Great explanation
Great Channel - Learning a lot
Thank you Douglas, glad you are enjoying the videos.
It looks like generate_sms() is not called as part of the new code. How will you retractor the codes so it is included when the class authorise_sms is used and excluded while authorise_robot is used?
I have a question. Composition is for example if a class Car has an attribute of another class ie Engine and if the outer and inner object have the same lifetime. Otherwise its an aggregate. So if each Car has the same engine by default(engine gets created in car) its not dependency injection only aggregation. If the engine is created outside of Car an is passed through init to assign to Car.engine it is both injection and aggregation. If Car has no attribute engine but uses the engine in one of its method that expects Engine it is only dependency injection. Is what I said correct?
I'm new to python, I'm wondering why use an Abstract class for the Authorizer, why not use an interface?
Incredible video, keep up the awesome work! :)
Thank you! Will do!
Is dependency inversion just Dependency Injection + Strategy Pattern?
Amazing videos - keep it up!
AMAZING VIDEOS! THANKS!
Awesome!!!
hello, authorizer is abc and has common functions, but when I give the sms method to Payment Processor, and intellisense like authorizer.get_sms_code() does not appear, how can I provide this?
Is there a video where you explain the difference between a design "pattern" and "principle" ? It wouldn't shock me that dependency injection be called a "principle", same if dependency inversion was a "pattern". Is there a clear-cut difference between both or is it fuzzy ?
The way I view it is that a principle is more of a guideline, whereas a pattern is a specific solution to a design problem. In that sense, "dependency inversion" is a principle because you can apply it in many different ways, whereas the "strategy" is a design pattern, because it prescribes a specific organization of classes and methods. To draw a carpentry analogy: a design principle is "make your wooden construction such that it can support at least twice the weight of what's needed"; a design pattern is a dovetail joint.
This is great, thank you.
Thank you, glad you liked the video!
Great video but I am a bit confused. You removed the authorizer.generate_sms_code() from the actual code but you used it in the test case. You said you would go back to it but I seem to miss it. Isn’t that very important in the actual code? Where is it going to be run?
in my understanding generate_sms_code() should be called inside the authorize method
If I understood this correctly an extra layer of abstraction is created between the Authorizer subclasses and the PaymentProcessor, where do we define which Authorizer is used? Am I missing something
I think I got it, you pass this on the test by setting each auth on the various authorizers, something that would be the case should the code is implemented
Another great video!
Where can I get the picture from the background?
Thanks Pascal, that's just an image I created myself using a photo from Unsplash + added the two colors. If you send me an email at business@arjancodes.com, I'd be happy to share it with you.
Do you think the initializer test is actually needed? Aren't you testing something that belongs to the python codebase? Instances of classes should be of the expected type
Excellent video
Thank you!
test_init couples your test to the implementation. if you decide to refactor and rename the variable to _authoriser, the test will break. IMHO you should test the public interface of the class to allow free refactoring of your implementation later. Technically "authoriser" variable is public in this example but maybe it shouldn't?
Indeed, in a production application I would definitely hide attributes behind properties or methods.
@@ArjanCodes I understand that you want to simplify the examples to demonstrate something but at the same time there are people learning from your code and they may think that this is what they need to do. They don't know that you do not do it in production code. I really like your lessons but as a learning resource I think the code should be perfect and production ready.
But how do you inject objects deeper in call hierarchy??
good explanation
Thank you , ur vid is very helpful
Thank you, glad it was helpful!
This might just be a minor detail, but why did you choose "1234567" as the wrong auth code. Having to know that a valid auth code has to have exactly 6 digits seems like a hurdle for understanding. Why not use an auth code like "wrong1" or "invalid"?
A suggestion: can you please increase the font size of your IDE a little bit. It's impossible for me to watch your videos on the phone.
I've taken care of this in more recent videos.
Very good video, I will check the other you have now :-)
Thank you Jakub!
Nice!
as always good work
Thank you!
1:10 - Dependency Injection
1:38 - Dependency Inversion
Can you check subtitle in video? It seems to not support English.
Hey! Thanks for this, indeed, there was a problem, but I just fixed it! :)
Thank you!
You’re welcome!
Man you are awesome!
Happy you like the videos!
You are the best 🤩
Thanks so much Hakim, glad it was helpful!