I'm here to ask a question about the design pattern called Dependency Injection.
DI allows an object to know its dependencies through an interface and not through its implementation. In this way, the implementation can vary without the dependent object being aware of it. The great advantage of DI is the weak coupling between objects.
I understand how it works and what it is for, but it doesn't close me completely. The DI has the advantage of, as the quote says, the decoupling between classes... but it has a great disadvantage: that by passing an interface to my object, and not an object, I lose functionality, that is, I will only have the methods of the interface but not of the objects.
So, is it okay for an object to have no dependencies, while losing functionality? I know that it is not as I say it, surely there is something in which I am wrong and it does not have the mentioned disadvantage. But hey, as far as I understand I see it that way.
First, it must be understood that an interface is similar to a contract because it establishes the functionalities that those who implement it must provide.
This provides the advantage that the interface client "is not relevant" 1 to the internal behavior of the implementation, what really matters is that it complies with the functionality established by the interface. Although it is true that some methods of the class can no longer be accessed, what DI indicates is that these methods are not relevant to the functionality that the interface fulfills.
───────
1. Internal behavior is not relevant to the interface client, but software developers/designers are not. This happens mainly when it is required to change the implementation for some particular cases, such as applying optimizations or other design patterns.
I put two scenarios where you can see the importance of DI and where the methods of the class are not entirely relevant.
Scenario 1: Access to the data source directly. Basic example of a Dao pattern.
As explained in the comments and as you can see, the class
EntidadController
, which is a client of the interfaceEntidadDao
, does not need to know or use the methods that are not public in the class that implements said interface.Scenario 2: A new implementation of the same interface needs to be used. In this case, you need to streamline data access.
Given this new implementation, the client
EntidadController
can now choose between usingEntidadDaoBaseDatosImpl
orEntidadDaoCacheImpl
as an implementation for the interfaceEntidadDao
to use. Whichever class is chosen, the only relevant methods that the client will use are those that are designated in the interface. The functionality of the classes that implement the interface is not lost, it is simply not used directly by the client.Now, this question may arise: What if I really need to access some method declared in the implementing class that is not in the interface? Well the answer is it depends :
public
in the implementing class).There is no correct answer to topics like this, you must evaluate each case and decide what is best for the situation you face.
When you declare a dependency on an interface it is because the interface has everything you need to perform the task you require from that interface. If the class that implements the interface has other methods, they should be irrelevant to client code.
In a good design, the interface must provide the necessary methods so that the clients of the class that is going to implement the interface can carry out their work.
It's quite common when using Dependency Injection for the methods of the interface to correspond one-to-one with the public methods of the implementing class so this shouldn't be a problem.
If you want to use the additional methods that are not in the interface just add them to the interface.
There is no loss of functionality, it is something different, an interface is a contract that defines certain functionality , by definition it cannot limit it.
It happens that interfaces introduce complexity to the code, but that complexity is justified by its advantage: Separating functionality from implementation.
Imagine that you design an application, you start with the component design, then you define the interfaces of each component and the dependencies to other interfaces, with that alone you already have the entire design. Then you can implement each component separately, with its unit tests where you mock the other interfaces and test each line of component code under different well-controlled conditions (by mocks), and of course, then you can (or not) use dependency injection to "wire" the system when you finished all the development.
Beyond Java's own interfaces, there are a number of things that are worth separating with interfaces whenever possible:
The data model , the base, all that. The justification is not because, perhaps, tomorrow, you will change from MySql to Oracle. The ultimate reason is that it is impossible, by design , to include database access details within other modules, making the code cleaner "by design". Interface examples: MyTableN, and MyTablesProvider and POJO.
Network/Disk/Peripherals : That is, Input Output, and not returning File or Socket type objects, in these interfaces return and receive input/output streams or similar. Examples: InputStream, OutputStream, Reader, MiNetworkProvider, MiFileSystemProvider.
Other examples: LOGS or application registration, third-party libraries (via wrapper), REST API.