Dependency inversion by example

In the book "Clean Code" all the time it is said that the inversion of dependencies is a wonderful thing. Probably, this is true due to the professionalism and experience of the author, but I understood how it is possible to "refer only to abstractions, without specifying their specific implementations".

I will present my train of thought, as I tried to understand it, starting with hard-coded code. In the Java code below (the method environment is omitted), Realization is one of the implementations ISomeInterface:

ISomeInterface example = new Realization();

Here, the variable variable is of type ISomeInterface, but the class Realization is something we must explicitly import into the file!

Further, I know that the Spring framework gives us a context from which to get specific instances of classes (dependency injection). This is better than the example above, but we still need to specify the name of the bean in the Java code! It turns out that there is a link to a specific implementation, but there is no direct import of the class to the file.

Well, plus it is not clear, what to do when there is no framework like Spring (especially since at the moment my main language is TypeScript). So in this question, I ask you to show an example of how it is possible to refer to abstractions without importing its implementation into the module.


Author: Боков Глеб, 2020-01-08

1 answers

It all comes down to the fact that you should make the most of the interfaces and not the specific implementation. For any of your classes that perform any actions on an entity, you write an interface, for example:

 public interface PersonService{
    ...
    Person getPerson(Long id);
    ...
 }

All other classes know nothing about the implementation of this method. They only use the getPersons(Long id) method inside themselves. Accordingly, they know nothing about the implementation of this method except what they should pass to it as input and what it returns. You don't have to in others classes use specific implementation methods, you only use the methods that are in the interface.

In this way, you reduce the connectivity of classes. The point is that the class itself, for example PersonControllerImpl, should not change in any way if you change the internal implementation of the method in the service or make a different implementation of the service interface.

For example

public class PersonControllerImpl{

    private PersonService service;

    PersonControllerImpl(PersonService service){
        this.service = service;
    }

    Person getPerson(Long id){
        service.getPerson(id); 
    }
} 

Your controller doesn't know anything about the service implementation. Of course there will be some method that will do new PersonServiceImpl() and will pass it to the input to the controller, but the controller itself does not depend on the implementation in any way. For example:

class PersonsContainer{

    private PersonService personService;
    private PersonController personController;

    PersonsContainer(){
        personService = new PersonServiceImpl();
        personController = new PersonControllerImpl( personService );
    }

}

With this approach, all you need to replace the service implementation class is to change one line in the container class.

Spring at startup also creates all instances of specific entities, and when you implement a dependency on the interface, it finds its implementation, if it does not find it, it simply will not start. Spring has taken out the duty of creating objects and a number of other tasks in their classes, which collectively called the container.

What it gives:

  • so that when changing one class, you don't have to change half of the program in a chain;
  • the ability to reuse classes in other projects.
 4
Author: Z.John, 2020-01-08 07:09:39