- Who is going to determine what classes (that implement
SwitchableDevice
) need to be called?
- Who tells
Button
what devices he need to turn on/off?
- How do you tell an object that uses something abstract which concrete things it needs to use? (Please correct me if this question is completely wrong)
The term often used is "wiring", which refers to the activity of connecting decoupled classes together. It typically involves connecting abstract interfaces to concrete classes, and managing the injection of each concrete instance when the application starts.
Wiring commonly happens in a top-level part of a program near the entry point known as the Composition Root. That might be a method called by main()
or it might even be main()
itself. (This isn't always the case though; for example, ASP.NET handles wiring on a per-HTTP-request basis, so this is more about typical use cases than hard/fast rules)
The analogy could be similar to wiring an electronic system which has a lot of small components that need to be connected together in order to create a working system; e.g. wiring up the many components of a robot which has all kinds of moving parts and circuit boards.
The motivation for this approach to software construction is to be able to treat an application like a set of independent, disconnected, cohesive "building blocks" which are fused together at the top-level of the application in order to make something which works.
Each building block of an application may represent (or take responsibility for) an aspect of that application's functionality. For example, an object repository, a factory, an interface to a network messaging system, a business rules engine, an event processing system, an interface to an external API, a "main menu" UI, etc.
Splitting classes/components out like this alllows each component to be developed on its own, with its own independent development lifecycle, its own unit tests, and its own identity; agnostic to other components of the application.
Many applications have some kind of shell or controller type component which hosts the user interface (GUI app) or main thread (service/console app) which bridges the 'gap' between the UI/UX and the rest of the app components.
Breaking an application's behaviour into many smaller classes often results in a number of single instance, (mostly) stateless objects whose lifetime matches that of the application, but which still need to interact with each other; these types of classes are ideal candidates for Dependency Injection.
There there are plenty of cases where application behaviour might come from throwaway, short-lived, stateful objects too - sometimes these are also injected, but they can be a little more awkward to deal with, so these sometimes get encapsulated inside other patterns such as Strategy, Factory, etc.
Some very simple c# code for Bob Martin's example might look like:
static void Main(string[] args)
{
ISwitchableDevice switchableDevice = new Lamp();
var button = new Button(switchableDevice);
// TODO - wiring for other classes which use button and switchableDevice
}
I don't think Uncle Bob's example on it's own is really sufficient to demonstrate DI/IoC in a real application - it only demonstrates constructor injection as an alternative to object ownership.
Here's a very contrived potential example of a background service whose responsibility is to listen for network control messages and toggle a lamp on and off:
static void Main(string[] args)
{
ISwitchableDevice switchableDevice = new Lamp();
var button = new Button(switchableDevice);
var messageListener = new NetworkMessageListener();
var serviceController = new ServiceController(messageListener, button);
// The wiring is done, now start the main thread and block...
serviceController.StartMainThread();
Thread.Wait();
}
In the above example, I'm assuming that the app is made up of 4 different classes, all of which are shown in the Main
method; those classes don't need to call new
to create other classes because Main()
(the composition root) uses poor-man's DI to create the various components of the application and wire them all up together.
You can assume that each concrete class' constructor accepts its dependencies using some interface
, and that none of those classes actually know about each other.
The above pattern scales out very well - when a new component needs to be added to the application, it's added into the composition root, and the composition root handles its wiring/dependencies (and/or injects it into one of the existing components if necessary).
You apply DIP when crossing a significant boundary. What's on either side doesn't matter. What matters is having a good reason to keep what's on one side from having a source code dependency on the other side.
The magical thing DIP does is let you enforce that source code dependency rule while allowing flow of control to go in and out of that boundary without resorting to using return.
When organized like this the assumption is that the inner stuff is more stable than the outer stuff. DIP lets you strip off the outer stuff and add new outer stuff without breaking the inner stuff.
When you hear the words "higher" and "lower" understand those terms were developed when the style was to keep all inheritance arrows pointing up. These round diagrams make that metaphor a little meaningless. Here's a diagram that tries to give them meaning again:
This doesn't change your design at all. Just gives those words meaning again.
And now the question: Would it be wise to apply DIP on all of these sub-services too? They are just internals of the service implementation and would not serve a purpose outside of the domain.
I'll stick with the significant boundary rule here. Like anything, DIP can be over applied. I wouldn't be surprised to find every service that faced a significant boundary applying DIP. I would be surprised to find nests of DIP applied to every module in the service layer even if they don't face a significant boundary.
Now that said, circular dependencies in source code are a special kind of hell. DIP can also be used to help avoid that. In all cases, be sure you understand why you're applying it. Don't just apply it cause someone told you to. Know why.
In other words: The official definition of DIP talks about high level and low level modules. Should all (sub-)services be considered one module or are they all separate modules?
The rings in these diagrams depict layers not modules. Now sure, a layer could have only one module in it but it can easily have more. Here a layer is a bag of modules that are asked to follow the layers rules when they touch modules in a different layer.
I can't speak for every author but I have heard Robert Martin explain module to mean object if you happen to be in an object oriented context. Modularization is an older philosophy of designing so parts are easy to remove and swap. You don't have to be object oriented to use modules.
In an OOP context I'd expect one-to-many objects to make up a service and one-to-many services make up a layer (ring).
While Onion, Hex, Ports, and Clean all amount to the same idea they each have their own vocabulary (designed to drive you to different books and blog posts). Because of that be careful when asking these questions because some words mean different things with different authors.
Best Answer
In many cartoons or other media, the forces of good and evil are often illustrated by an angel and a demon sitting on the character's shoulders. In our story here, instead of good and evil, we have SOLID on one shoulder, and YAGNI (You ain't gonna need it!) sitting on the other.
SOLID principles taken to the max are best suited for huge, complex, ultra-configurable enterprisey systems. For smaller, or more specific systems, it is not appropriate to make everything ridiculously flexible, as the time you spend abstracting things will not prove to be a benefit.
Passing interfaces instead of concrete classes sometimes means for example that you can easily swap reading from a file for a a network stream. However, for a great amount of software projects, that kind of flexibility is just not ever going to be needed, and you might as well just pass concrete file classes and call it a day and spare your brain cells.
Part of the art of software development is having a good sense of what is likely to change as time goes on, and what isn't. For the stuff that is likely to change, use the interfaces and other SOLID concepts. For the stuff that won't, use YAGNI and just pass concrete types, forget the factory classes, forget all the runtime hooking up and configuration, etc, and forget a lot of the SOLID abstractions. In my experience, the YAGNI approach has proven to be correct far more often than it is not.