Every time an object is created within a function, the function's dependency on that object's class appears. In other words, the function is hardwired to work in tandem with a specific class. There is a formal way to easily check how much your code is tied up. Take any function and imagine you're transferring it to another project. How many dependencies does it entail (how many do they in turn entail their own dependencies)? If moving a function would require moving a lot of code, then the code is said to have high cohesion.
A special term has even been coined for code decoupling:Dependency Inversion Principle. It's also known as DIP from SOLID. Here's its wording:
Depending on the language, these phrases have slightly different meanings. By and large, they're saying that you don't need to get caught up in a particular implementation of a class. Creating objects where they're used binds us to the class of those objects without us being able to change it. The correct approach, in terms of this principle, is to invert dependencies, that is, not to work with classes directly, but to get objects from the desired classes from outside, for example, through function parameters.
Also, the DIP talks about binding to interfaces instead of classes in function signatures. We'll talk about this later, once we're done with the basic concepts.
Before:
const doSomething = () => {
const logger = new Logger();
// some code
};
After:
const doSomething = (logger) => {
// some code
};
When talking about DIP, people like to use the Hollywood principle as an analogy: Don't call us, we'll call you. What this means is that you don't need to use classes directly, you can instead get ready-made objects as an external dependency.
Do we always have to stick to this principle? Frankly speaking, code built entirely in this style becomes excessively abstract and difficult to understand. There are no magic wands you can wave in programming and in each situation you need to look at the conditions and the problem to be solved. If we need to change our implementation, we do so, if not, we work directly.
When talking about dependency inversion, discussions about dependency injection are not far behind. While the DIP talks about modularity, dependency injection talks about specific ways to achieve it. About how you can pass dependencies to code that uses them. There are a total of three ways to inject dependencies:
Pass them as arguments to functions or methods. This is the method we've used so far.
doSomethingUseful(new Logger());
Via a constructor in situations where objects are used.
const app = new Application(new Logger());
Via setters. It's best not to use this method if possible. It has to do with changing objects and breaking integrity (more details in our course on object-oriented design).
const app = new Application();
app.setLogger(new Logger());
As you can see, behind this fancy term we have one very simple thing – parameter transfer. However, these terms allow you to understand more meaning without having to know additional context. The main thing is not to get carried away, or you'll end up as an architectural astronaut.
The Hexlet support team or other students will answer you.
A professional subscription will give you full access to all Hexlet courses, projects and lifetime access to the theory of lessons learned. You can cancel your subscription at any time.
Programming courses for beginners and experienced developers. Start training for free
Our graduates work in companies:
From a novice to a developer. Get a job or your money back!
Sign up or sign in
Ask questions if you want to discuss a theory or an exercise. Hexlet Support Team and experienced community members can help find answers and solve a problem.