Without dependency injection
In the following Java example, first of all, let's see what is a dependency between two classes? Take a look at the following class diagram:
As seen in the preceding diagram, the TransferService class contains two member variables, AccountRepository and TransferRepository. These are initialized by the TransferService constructor. TransferService controls which implementation of the repositories is used. It also controls their construction. In this situation, TransferService is said to have a hard-coded dependency on the following example:
Following is the TransferServiceImpl.java file:
public class TransferServiceImpl implements TransferService { AccountRepository accountRepository; TransferRepository transferRepository; public TransferServiceImpl(AccountRepository accountRepository,
TransferRepository transferRepository) { super();
// Specify a specific implementation in the constructor
instead of using dependency injection
this.accountRepository = new JdbcAccountRepository(); this.transferRepository = new JdbcTransferRepository(); }
// Method within this service that uses the accountRepository and
transferRepository
@Override public void transferAmmount(Long a, Long b, Amount amount) { Account accountA = accountRepository.findByAccountId(a); Account accountB = accountRepository.findByAccountId(b); transferRepository.transfer(accountA, accountB, amount); } }
In the preceding example, the TransferServiceImpl class has dependencies of two classes, that is AccountRepository and TransferRepository. The TransferServiceImpl class has two member variables of the dependent classes, and initializes them through its constructor by using the JDBC implementation of repositories such as JdbcAccountRepository and JdbcTransferRepository. The TransferServiceImpl class is tightly coupled with the JDBC implementation of repositories; in case the JDBC implementation is changed to a JPA implementation, you have to change your TransferServiceImpl class as well.
According to the SOLID (Single Responsibility Principle, Open Closed Principle, Liskov's Substitution Principle, Interface Segregation Principle, Dependency Inversion Principle) principles, a class should have a single responsibility in the application, but in the preceding example, the TransferServiceImpl class is also responsible for constructing the objects of JdbcAccountRepository and JdbcTransferRepository classes. We can't use direction instantiation of objects in the class.
In our first attempt to avoid the direct instantiation logic in the TransferServiceImpl class, we can use a Factory class that creates instances of TransferServiceImpl. According to this idea, TransferServiceImpl minimizes the dependency from AccountRepository and TransferRepository--earlier we had a tightly coupled implementation of the repositories, but now it refers only to the interface, as shown in the following diagram:
But the TransferServiceImpl class is, again, tightly coupled with the implementation of the RepositoryFactory class. Moreover, this process is not suitable for cases where we have more number of dependencies, which increases either the Factory classes or the complexity of the Factory class. The repository classes can also have other dependencies.
The following code uses the Factory class to get the AccountRepository and TransferRepository classes:
Following is the TransferServiceImpl.java file:
package com.packt.patterninspring.chapter4.bankapp.service;
public class TransferServiceImpl implements TransferService { AccountRepository accountRepository; TransferRepository transferRepository; public TransferServiceImpl(AccountRepository accountRepository,
TransferRepository transferRepository) { this.accountRepository = RepositoryFactory.getInstance(); this.transferRepository = RepositoryFactory.getInstance(); }
@Override
public void transferAmount(Long a, Long b, Amount amount) { Account accountA = accountRepository.findByAccountId(a); Account accountB = accountRepository.findByAccountId(b); transferRepository.transfer(accountA, accountB, amount); } }
In the preceding code example, we have minimized tight coupling, and removed direction object instantiation from the TransferServiceImpl class, but this is not the optimal solution.