tag:blogger.com,1999:blog-3827986091291063109.post3297744877526204952..comments2023-05-10T02:04:36.067+01:00Comments on Always Learning: Is Best Practice Actually Poor Practice? Dependency Injection, Type Hinting, and Unit Tests...Anonymoushttp://www.blogger.com/profile/17663647809732553575noreply@blogger.comBlogger13125tag:blogger.com,1999:blog-3827986091291063109.post-38588943224128842712017-05-05T13:25:14.833+01:002017-05-05T13:25:14.833+01:00Using DI does not mean your interfaces have to cha...Using DI does not mean your interfaces have to change. That would defeat the purpose of using an interface! Injection of dependencies is not relevant to interfaces (it would not make sense to have a constructor defined in an interface for example).<br /><br />Also, your parting dichotomy is not valid - a DI container can provide different dependencies to the same class to create different implementations. The different implementations would just have different keys in the container. Even if the dependencies are the same every time, there is no harm in using DI, and it brings benefits outlined in the article (loose coupling, use of mocks, and self-documenting).Anonymoushttps://www.blogger.com/profile/17663647809732553575noreply@blogger.comtag:blogger.com,1999:blog-3827986091291063109.post-61858458273852578212017-05-03T13:54:38.797+01:002017-05-03T13:54:38.797+01:00I agree completely, but the main argument against ...I agree completely, but the main argument against using best practice that I have heard is that it is slower and requires more work. So I try to acknowledge that there is a cost/benefit calculation to be done.Anonymoushttps://www.blogger.com/profile/17663647809732553575noreply@blogger.comtag:blogger.com,1999:blog-3827986091291063109.post-56195087099662288062017-05-03T13:35:47.022+01:002017-05-03T13:35:47.022+01:00I didn't say DI was the only way to decouple o...I didn't say DI was the only way to decouple or re-use code. Clearly it isn't - there is nearly always more than one way to do things. So no disagreement there.<br /><br />I agree that DI will often require you to write more code, which in a rapid application development scenario might not be worthwhile. However, in this article I am referring to enterprise development of code that needs to be supported for many years to come. The benefits of DI nearly always outweigh the extra legwork in that scenario. So still not necessarily any disagreement there.<br /><br />The example you gave (passing a factory into an entity to create a child property) is exactly what I said in the article was an example of when DI is not appropriate and it is perfectly acceptable to create a new object on the fly. No disagreement there either.Anonymoushttps://www.blogger.com/profile/17663647809732553575noreply@blogger.comtag:blogger.com,1999:blog-3827986091291063109.post-36144205291836053462017-04-25T13:19:49.047+01:002017-04-25T13:19:49.047+01:00> it decouples classes, and facilitates code re...> it decouples classes, and facilitates code re-use even when implementation details differ.<br /><br />... but DI is *not* the only way of instigating weak coupling between classes, nor is it the only way of facilitating code re-use. It's entirely appropriate to use inheritance and composition to achieve this.<br /><br />> By advertising what the dependencies are and requiring the mandatory ones to be passed in, the code also becomes self-documenting<br /><br />I disagree on the "self-documenting" nature of DI. I find DI-structured code far less readable, because it entails significant boilerplate and unnecessary class variables.<br /><br />Here's me creating a new Address object:<br /><br />class Person<br />{<br /> private $address;<br /><br /> function setAddress($line1,$line2,$postcode) {<br /> $this->address = new Address($line1, $line2, $postcode);<br /> }<br />}<br /><br />Here's the same code in DI-land:<br /><br />class Person<br />{<br /> private $address;<br /> private $addressFactory;<br /><br /> __construct($addressFactory") {<br /> $this->addressFactory = $addressFactory;<br /> }<br /><br /> function setAddress($line1, $line2, $postcode) {<br /> $this->address = $this->addressFactory->createAddress($line1,$line2,$postcode);<br /> }<br />}<br /><br />> less brittle, easier to extend, and easier to debug<br /><br />As Tony Marston said in his post, DI is great for the situation where you actually need to inject a class conforming to a particular interface into your class to do work, where the implementing object is likely to be different every time. My experience with DI is that, containers or no, 90% of objects passed through DI are unchanging config or addresses of services that themselves are singletons. There is really no benefit to juggling these Factories and Services around through dependency injection.<br /><br />And it's certainly waaaaaaaaaaay easier to debug code that doesn't pass through the multiple additional levels of indirection that DI requires.sanbikinoraionhttps://www.blogger.com/profile/15823745465626270567noreply@blogger.comtag:blogger.com,1999:blog-3827986091291063109.post-7209439445893661102017-04-25T13:08:55.193+01:002017-04-25T13:08:55.193+01:00> If your default implementation must change th...> If your default implementation must change though, you only have to update the dependency injection container, and/or any factories.<br /><br />... whereas in my world, when I change the implementation of a class, I don't have to change the interface signature at all. I maintain my interface contracts. That's the core OO principle of encapsulation.<br /><br />The use of DI containers to pass in necessary dependencies is just silly:<br />* Either those dependencies are the same every time -- in which case they could simply be instantiated in the constructor...<br />* Or they are different every time -- in which case, a DI container cannot prefill them for you.sanbikinoraionhttps://www.blogger.com/profile/15823745465626270567noreply@blogger.comtag:blogger.com,1999:blog-3827986091291063109.post-1817127324288091142017-04-24T19:43:50.697+01:002017-04-24T19:43:50.697+01:00DI is not just 'because unit testing' - it...DI is not just 'because unit testing' - it decouples classes, and facilitates code re-use even when implementation details differ. By advertising what the dependencies are and requiring the mandatory ones to be passed in, the code also becomes self-documenting, less brittle, easier to extend, and easier to debug (and yes, easier to unit test).Anonymoushttps://www.blogger.com/profile/17663647809732553575noreply@blogger.comtag:blogger.com,1999:blog-3827986091291063109.post-79868847310455895542017-04-24T19:35:16.235+01:002017-04-24T19:35:16.235+01:00I think the main point of the open/closed principl...I think the main point of the open/closed principle is that you try not to change the underlying implementation (closed for modification). If you need a different implementation, you extend - which does not break any of your existing code. If your default implementation must change though, you only have to update the dependency injection container, and/or any factories. This is one of the reasons I advocate limiting the use of the new keyword to those places as much as possible.<br /><br />The problem of needing to supply all the dependencies (and dependencies of dependencies) before being able to use a service, is easily solved with a dependency injection container. A DI container and a service locator are essentially the same thing, just used slightly differently - the DI container does not hide the dependencies, but it can still resolve them for you (whereas when a container is used as a service locator, it both hides and resolves dependencies).Anonymoushttps://www.blogger.com/profile/17663647809732553575noreply@blogger.comtag:blogger.com,1999:blog-3827986091291063109.post-41809570288084293802017-04-24T17:13:22.866+01:002017-04-24T17:13:22.866+01:00Separately, it's always been possible to do un...Separately, it's always been possible to do unit testing on PHP without DI, by ringfencing the "new" keyword in its own class method that the unit-under-test can call upon, allowing it to be mocked. I agree with your original correspondant that "because unit testing" is not a good enough reason alone for DI, when other alternatives are available. A better language than PHP would surely allow language keywords to be overridden for testing purposes.sanbikinoraionhttps://www.blogger.com/profile/15823745465626270567noreply@blogger.comtag:blogger.com,1999:blog-3827986091291063109.post-90912362067580164342017-04-24T17:11:09.310+01:002017-04-24T17:11:09.310+01:00So I too have a long-running aversion to DI and, b...So I too have a long-running aversion to DI and, by referencing SOLID principles to defend it, you have enlightened me as to a framework I can finally hang my nagging feeling of discontent upon.<br /><br />The 'O' of SOLID is the "open/closed" principle -- cf. Wikipedia: "software entities … should be open for extension, but closed for modification".<br /><br />Furthermore, a key principle of OO software design is that the implementation should be separated from the interface.<br /><br />But to my mind, DI fails to accomplish both of these. In my experience, it seems that method signatures are very often changed to add new parameters when the underlying implementation changes, when the underlying implementation suddenly requires a new piece of information to depend on. That can lead to a cascade of changes throughout your codebase as additional dependencies must be passed through a whole chain of classes in order to satisfy the new interface spec.<br /><br />Secondly, and concomitantly, DI requires that consumers of a service know exactly and all services upon which that service depends, and must supply them all. Again, as the implementation changes, the interface must also change.<br /><br />The workarounds for this, in the form of the service locator (which often supplies many dependencies for the desired service by magic at runtime, or in the form of cramming all the dependencies into the class constructor in order to minimize interface-failure-cascade, both seem to me to be unsatisfactory solutions -- but I am at a loss for what the correct solution would be.sanbikinoraionhttps://www.blogger.com/profile/15823745465626270567noreply@blogger.comtag:blogger.com,1999:blog-3827986091291063109.post-18358945637605315672017-04-07T08:32:04.853+01:002017-04-07T08:32:04.853+01:00Enjoyed your post, just wanted to pick up on one p...Enjoyed your post, just wanted to pick up on one point:<br />"...creating software with a short lifecycle (that may be discarded in a few months' time)" I have found that all quick and dirty throw away projects soon become a company bedrock and the pain point of developers for years to come.Anonymousnoreply@blogger.comtag:blogger.com,1999:blog-3827986091291063109.post-55828172785242839582017-04-06T07:32:43.963+01:002017-04-06T07:32:43.963+01:00Makes sense, thanks for the correction!Makes sense, thanks for the correction!Anonymoushttps://www.blogger.com/profile/17663647809732553575noreply@blogger.comtag:blogger.com,1999:blog-3827986091291063109.post-38092904639183182852017-04-06T07:20:50.045+01:002017-04-06T07:20:50.045+01:00Since you're correcting misconceptions, I have...Since you're correcting misconceptions, I have to correct you on one very common misconception: unit tests in OOP are, yes, usually, but not always a test for a single class. It would have been called a "class test" then - the term "unit" does not mean "class" or "function", it means the smallest testable unit of code, which is sometimes more than one class.<br /><br />Take this class for example:<br /><br />https://github.com/mindplay-dk/timber/blob/master/src/Router.php<br /><br />It internally uses a second class (Route) to maintain its internal table of routes. It's an implementation detail, as is proven by the fact that it could have been refactored to internally use a different class or arrays or anything else that solves the problem, as long as it's public API remains unaffected.<br /><br />This comes back to the dependency injection question. In this case, you can't say that Route is a dependency - it's an implementation detail. Dependency injection is for dependencies, and Route objects are not.<br /><br />When you're testing the Router, that's a unit test - it doesn't involve integration with any kind of external dependency, service, class, etc. which would make it an integration test. The Router class can stand alone, so it is a single unit from the clients perspective: I don't need to supply it with any dependencies for it to function, and that's the real distinction, as I understand it. (The exception being code that internally uses facades or singletons etc. to hide its dependencies!)mindplayhttps://www.blogger.com/profile/15880265624033069679noreply@blogger.comtag:blogger.com,1999:blog-3827986091291063109.post-31475137334423743512017-04-05T07:51:44.890+01:002017-04-05T07:51:44.890+01:00Well this can become a very debatable topic. But I...Well this can become a very debatable topic. But I like your views and explanations. Learning code is easy, but learning to do it in a right way is what makes it difficult.Saquib Rizwanhttp://cloudways.comnoreply@blogger.com