依赖注入(Dependency Injection,DI)和依赖倒置(Inversion of Control,IoC)是很长时间以来的两个热门主题。它们经常放在一起来谈论,甚至很多人会把它们看做是同一个概念。但实际上,它们是不同的,但又密不可分的概念。
依赖倒置
依赖倒置是一种设计原则,它指出一个高层模块不应该依赖于一个底层模块,两者都应该依赖于抽象。同时,抽象不应该依赖于具体实现,具体实现应该依赖于抽象。这个理念很好地描述了什么是“高内聚,松耦合”。这样做的最大好处是,我们可以在不改变底层代码的情况下改变高层业务逻辑,从而提高整个应用的灵活性和可扩展性。
依赖注入
依赖注入是一个实现依赖倒置的方法。简单地说,依赖注入是将依赖关系的创建和绑定从调用者移动到了被调用者。它是一种通过构造函数、Setter方法、接口等方式,将依赖对象的创建过程交给Spring Framework进行控制的技术。依赖注入是将依赖对象引用注入到对象中的过程,而不是通过对象本身去获取依赖对象。这样做的好处是,我们可以将组件的实现和组装分离开来,从而大大提高了代码的复用性和可维护性。
依赖注入和依赖倒置的关系
依赖注入是实现依赖倒置(IoC)的一种方式。IoC 是一种设计原则,它指出一个高层模块不应该依赖于一个底层模块,两者都应该依赖于其抽象,而不是具体实现。这种设计思想可以提高代码的灵活性、可读性和可维护性。
依赖注入是一种实现 IoC 的方法,通过它可以将一个对象的所有依赖对象的创建过程从该对象自身的代码中移除,转交给 IoC 容器,然后由 IoC 容器控制整个依赖关系的创建和绑定。
下面我们通过两个简单的示例来进一步说明依赖注入和依赖倒置的概念。
示例1:依赖注入
我们假设有一个UserService类,这个类需要依赖于一个UserDao类。我们可以通过依赖注入的方式将UserDao类的实例注入到UserService类中。
定义UserDao接口:
public interface UserDao {
void save(User user);
}
定义UserServiceImpl类:
public class UserServiceImpl implements UserService {
private UserDao userDao;
// 构造函数注入
public UserServiceImpl(UserDao userDao) {
this.userDao = userDao;
}
public void saveUser(User user) {
userDao.save(user);
}
}
通过构造函数,将UserDao的实例注入到UserServiceImpl类中。
在Spring配置文件中配置UserDao和UserServiceImpl的实例:
<bean id="userDao" class="com.example.dao.UserDaoImpl" />
<bean id="userService" class="com.example.service.UserServiceImpl">
<constructor-arg ref="userDao" />
</bean>
通过以上配置,我们就可以使用UserServiceImpl类了,Spring会自动将UserDao的实例注入到UserServiceImpl中。这种方式使得UserService类不再依赖于具体的UserDao实现,从而提高了代码的复用性和可维护性。
示例2:依赖倒置
我们再假设有一个ReportService类,这个类需要依赖于一个数据访问类 DataAccess。如果按照传统的方式实现,ReportService类将直接依赖于具体的 DataAccess 实现类,从而导致耦合程度较高。但是,如果我们采用依赖倒置的方式,定义一个抽象的数据访问接口 IDataAccess,并将 DataAccess 实现类实现这个接口,那么,ReportService 类就只需要依赖于 IDataAccess 接口,而不是具体的 DataAccess类。
定义IDataAccess接口:
public interface IDataAccess {
void getConnection();
}
定义DataAccessImpl类:
public class DataAccessImpl implements IDataAccess {
public void getConnection() {
// TODO
}
}
定义ReportService类:
public class ReportService {
private IDataAccess dataAccess;
public ReportService(IDataAccess dataAccess) {
this.dataAccess = dataAccess;
}
public void generateReport() {
dataAccess.getConnection();
// TODO 生成报告
}
}
通过以上代码,我们将数据访问模块 DataAccess 的具体实现从 ReportService 类中抽离出来,使得 ReportService 类只依赖于 IDataAccess 接口。而具体的 DataAccess 实现类实现了 IDataAccess 接口,可以通过依赖注入的方式注入到 ReportService 类中。
通过依赖倒置和依赖注入的技术,我们可以将应用程序的控制权从底层组件移动到高层模块之上,使得应用程序更加模块化、可扩展和易于维护。