在最开始的时候,大家可能都会想,我们习惯于直接把业务代码写在Controller中多方便;后面出现MVC框架,我们就把业务代码写在Service中;现在为何还要先写个接口,再麻烦的写一个实现类?我最开始学习的时候也是有这个疑惑,下面给大家解答疑惑。
在Java中,使用implements
关键字来实现接口是一种面向接口编程的重要实践,它带来了以下几方面的好处:
-
松耦合:接口定义了服务提供者应遵循的规范,而具体实现与接口分离。这样,当服务的实现需要改变时(比如从MySQL迁移到MongoDB),只要修改实现类而不影响到依赖该接口的其他模块,提高了系统的灵活性和可维护性。
-
多实现:一个接口可以有多个实现类,每个实现类可以根据不同需求提供不同的实现方式。这为程序设计提供了高度的灵活性和扩展性。
-
标准化编程:接口定义了一组公共的行为标准,鼓励遵循统一的编程规范,使得代码更加规范和易于理解。对于大型项目或团队开发,这一点尤为重要,有助于保持代码风格的一致性。
-
利于测试和 mock:在进行单元测试时,可以轻松地使用Mock对象替换真实的实现,因为测试代码只依赖于接口,而不是具体的实现细节。这使得测试更加聚焦,也更容易构造和控制测试环境。
-
设计先行:在实际编码之前设计接口,可以帮助开发者在更高层次上思考系统架构和组件间的交互,促进良好的软件设计。接口定义了“应该做什么”,而非“如何做”,有助于清晰地划分职责。
-
提高代码重用性:接口作为抽象层,可以在不同的项目或模块中重用,减少重复代码,提高代码的复用率。
因此,虽然直接编写Service类看起来可能更直接简单,但通过定义接口并实现它,能够为长期的项目维护、扩展和团队协作带来显著的优势。
举个简单的例子说明一下:
假设我们正在开发一个用户管理系统,其中有一个需求是能够保存用户信息到数据库中。按照面向接口编程的原则,我们可以这样做:
首先,定义一个用户服务接口 (UserService.java
):
public interface UserService {
void saveUser(User user);
}
在这个接口中,我们声明了一个方法 saveUser
,用于保存用户信息。接口不包含任何具体的实现,它只是定义了一个服务应当提供的功能。
然后,创建这个接口的一个实现类 (UserServiceImpl.java
):
import javax.annotation.Resource;
@Service // Spring注解,表示这是一个Service Bean
public class UserServiceImpl implements UserService {
@Resource // 假设使用Spring框架,注入UserDao实例
private UserDao userDao;
@Override
public void saveUser(User user) {
// 实现保存用户到数据库的逻辑
userDao.insert(user);
}
}
在这个实现类中,我们实现了 UserService
接口中的 saveUser
方法,并提供了具体的数据库操作逻辑。
现在,在其他需要使用用户保存功能的组件中,我们只需要依赖 UserService
接口,而不是具体的实现类。这样做的好处如下:
- 灵活性:如果未来需要更换数据库存储方式或添加额外的逻辑(例如,发送注册成功邮件),我们只需要修改
UserServiceImpl
类,而调用UserService.saveUser
的地方无需改动。 - 可测试性:在单元测试中,我们可以轻松地为
UserService
提供一个模拟实现(Mock对象),以便于测试业务逻辑,而不需要真实操作数据库。 - 多实现支持:如果有多种用户存储的需求(比如同时支持数据库存储和云端存储),我们可以为
UserService
接口提供多个实现类,系统可以根据需要选择合适的实现。
通过这个例子,可以看到使用接口和实现类的方式增强了代码的可维护性、灵活性和可测试性。
多个实现类的例子:
让我们基于前面的用户服务例子,展示如何通过多个实现类来支持不同的用户存储策略。假设我们现在除了要将用户信息保存到数据库外,还想支持将用户信息保存到云端存储(例如:云数据库或文件存储服务)。我们可以这样做:
首先,保持原有的 UserService
接口不变:
public interface UserService {
void saveUser(User user);
}
接着,保留原有的数据库实现类 UserServiceImpl
:
@Service("dbUserService")
public class UserServiceImpl implements UserService {
@Resource
private UserDao userDao;
@Override
public void saveUser(User user) {
userDao.insert(user);
}
}
然后,新增一个云端存储的实现类 CloudUserService.java
:
@Service("cloudUserService")
public class CloudUserService implements UserService {
private final CloudUserRepository cloudUserRepository; // 假设这是一个操作云端数据库的客户端
@Autowired
public CloudUserService(CloudUserRepository cloudUserRepository) {
this.cloudUserRepository = cloudUserRepository;
}
@Override
public void saveUser(User user) {
// 实现将用户信息保存到云端的逻辑
cloudUserRepository.save(user);
}
}
这里,CloudUserService
实现了 UserService
接口,但是它的 saveUser
方法将用户信息保存到了云端存储中,而不是本地数据库。
最后,在需要使用用户保存服务的地方,根据实际需求选择不同的实现。可以通过Spring的依赖注入(@Autowired)并使用@Qualifier注解来指定具体的服务实现:
@Autowired
@Qualifier("dbUserService")
private UserService dbUserService;
@Autowired
@Qualifier("cloudUserService")
private UserService cloudUserService;
或者,如果你的应用场景允许动态选择服务实现,可以通过工厂模式或策略模式进一步封装这个选择过程。
通过这种方式,我们的系统变得更加灵活,可以根据不同的需求或环境条件轻松切换用户信息的存储策略,而无需修改大量现有代码。这就是使用多个实现类所带来的优势之一。
共同勉励,一起学习,加油。