问题背景:在重构一个项目中发现存在 RecipeService 类,基于之前的经验,有见到过其他项目使用服务层时会有对应抽象和实现,这样做的原因理解不是很深刻,所以对是否要添加IRecipeService 抽象?C# 只有一个实现的接口有意义吗?以此出发了解了一些东西。
以下大部分是另一篇博客文章的原文,个别是我记录添加的学习笔记。
在 Java 及其他面向对象编程语言的应用开发中,Service 层负责业务逻辑的处理,通常被设计为接口与实现分离的方式,即 IService + ServiceImpl 模式。然而,在实际项目开发中,很多开发者会质疑:“Service 层的接口是否真的有必要?” 许多小型项目中,Service 层的接口似乎只是多了一层冗余,没有实际作用。究竟接口对代码架构有哪些影响?是否在所有场景下都需要实现接口?
service层真的需要实现接口吗?service层的接口有什么用?
因为我是C#开发,有些Java上的知识不是很了解,标注一下。
DTO:
DTO(Data Transfer Object,数据传输对象)是 Java 后端开发中常见的设计模式之一。它的作用是在不同的层之间传输数据,特别是在网络或应用层之间进行数据交换时,提供一种简单的数据载体。DTO 本质上是一个不包含业务逻辑的纯数据对象,用于打包数据,便于在系统的不同部分传递
上面项目结构具体说不出来是哪种架构,不过我目前感觉Controller层与经典架构MVC中的Controller层相似,延伸到WPF 的MVVM中,VM层代替了Controller,所用在ViewModel中依赖了很多服务(Service),根据依赖倒置原则,上层应该依赖下层的抽象,所以依赖的是IService,比如在工控上的IMoveControlService(轴控服务),IVisionService(视觉服务)等;
以上是我的见解,我说的不一定对,还望各位看官如果有更深刻的理解的话,一起讨论;
在很多项目中,我们经常看到类似如下的代码结构:
public interface UserService {
User getUserById(Long id);
}
@Service
public class UserServiceImpl implements UserService {
@Override
public User getUserById(Long id) {
return new User(id, "Alice");
}
}
有些开发者会问:“既然 UserServiceImpl 只有一个实现,那为什么不直接使用 UserServiceImpl,而要多此一举定义一个 UserService 接口?” 这样做真的有意义吗?如果去掉接口,直接用 UserServiceImpl,代码是否会变得更简洁?还是会引发更大的问题?
1 Service 层的基本概念
Service 层在软件架构中的主要作用是封装业务逻辑,使得 Controller 层不直接操作 DAO 层,而是通过 Service 进行数据处理。通常,Service 层分为接口(IService)和实现类(ServiceImpl):
public interface OrderService {
Order findOrderById(Long id);
}
@Service
public class OrderServiceImpl implements OrderService {
@Override
public Order findOrderById(Long id) {
return new Order(id, "Product XYZ", 100);
}
}
这种结构在许多项目中被广泛使用,但也有开发者选择直接在 Controller 中调用 Service 实现类,而不定义接口。
2 为什么需要 Service 层接口?
2.1 遵循面向接口编程(OCP 原则)
在软件开发中,开闭原则(Open-Closed Principle, OCP) 提倡“对扩展开放,对修改封闭”。如果 Service 直接依赖于具体实现类,未来如果要更换 Service 实现,就需要修改 Controller 的代码。例如:
@RestController
public class UserController {
private final UserServiceImpl userService;
@Autowired
public UserController(UserServiceImpl userService) {
this.userService = userService;
}
}
这种方式直接依赖 UserServiceImpl,如果未来想要替换 UserServiceImpl 为另一个实现类,就必须修改 UserController,违背了开闭原则。而如果使用接口,Controller 只依赖接口,实现类可以随时更换:
@RestController
public class UserController {
private final UserService userService;
@Autowired
public UserController(UserService userService) {
this.userService = userService;
}
}
2.2 提高代码的解耦性
使用接口可以降低 Controller 与 Service 之间的耦合度,使代码更易维护。例如,如果未来需要引入缓存实现,我们可以创建新的 CachedUserServiceImpl,无需修改 UserController。
@Service
public class CachedUserServiceImpl implements UserService {
private final UserServiceImpl userService;
private final CacheManager cacheManager;
@Autowired
public CachedUserServiceImpl(UserServiceImpl userService, CacheManager cacheManager) {
this.userService = userService;
this.cacheManager = cacheManager;
}
@Override
public User getUserById(Long id) {
return cacheManager.getOrLoad(id, () -> userService.getUserById(id));
}
}
这段实例使用了设计模式中的装饰器模式?有继承有组合。我觉得这是
2.3 方便单元测试
如果 Controller 直接依赖 UserServiceImpl,单元测试就需要创建 UserServiceImpl 的完整实例,包括所有依赖。而如果依赖接口,我们可以使用 Mock 对象来测试:
@ExtendWith(MockitoExtension.class)
public class UserControllerTest {
@Mock
private UserService userService;
@InjectMocks
private UserController userController;
@Test
public void testGetUserById() {
when(userService.getUserById(1L)).thenReturn(new User(1L, "Alice"));
User user = userController.getUserById(1L);
assertEquals("Alice", user.getName());
}
}
不熟悉,可以参考:接口学习——1. 接口与单元测试_单元测试调用接口-CSDN博客
2.4 支持 AOP 代理
Spring AOP 主要基于代理机制,如果直接使用 UserServiceImpl,可能会影响 AOP 代理的正确性。通过接口,Spring 可以基于 JDK 动态代理增强方法调用,例如日志记录、事务管理等。
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.service.UserService.*(..))")
public void logBeforeMethod(JoinPoint joinPoint) {
System.out.println("Executing: " + joinPoint.getSignature());
}
}
看不懂,问了DP:
1)JDK 动态代理的工作流程
Spring 发现
UserServiceImpl
实现了UserService
接口。创建代理对象,代理的是
UserService
接口,而非UserServiceImpl
类本身。当调用
userService.saveUser()
时:先执行
LoggingAspect
的@Before
逻辑。再调用实际
UserServiceImpl.saveUser()
方法。
(2)如果直接使用实现类(没有接口)
java
// 错误用法:直接注入实现类,AOP 可能失效!
@Autowired
private UserServiceImpl userService;
问题:JDK 动态代理无法代理具体类,除非改用 CGLIB。
结果:
@Before
等切面逻辑不会被执行。
感觉是Spring实现AOP是通过的设计模式种的代理模式,接口可以完美适配这种模式?
3 什么时候可以不使用 Service 接口?
虽然 Service 接口有很多好处,但在某些情况下,它可能是不必要的:
3.1 项目规模较小,代码简单
如果是一个小型项目,Service 只有一个实现,并且未来不会扩展,去掉接口可以减少代码量,使代码更直观。
3.2 Service 只在一个模块内使用
如果某个 Service 仅在一个模块内使用,且不会被多个组件依赖,则可以省略接口,直接使用实现类。
4 结论
是否使用 Service 接口,取决于具体的项目需求。在大多数情况下,使用接口可以提升代码的解耦性、可维护性、可扩展性,并有助于 AOP 代理和单元测试。然而,在小型项目或简单业务场景中,直接使用 Service 实现类可以减少代码复杂度。
-----------------------------------
©著作权归作者所有:来自51CTO博客作者全栈技术开发者的原创作品,请联系作者获取转载授权,否则将追究法律责任
service层真的需要实现接口吗?service层的接口有什么用?
https://blog.51cto.com/u_16827017/13661875
-----------------------------------------------------------------------------------------------
另外的说法
第一种场景:解耦
如果实现类没有接口,如果有一天这个实现类不想用了,换成另一个实现类,众多方法调用了我的实现类中的方法,那么是不是每一个调用我实现类的都要改一下呢?起码注入的类要改成新类吧?这样不利于扩展和解耦,因为你改变了东西我们都要改原来写好的代码(你要不影响我以前代码的使用才行),耦合度太高了。
如果是实现接口了的话,你们调用我的接口,只要注入接口就行了。如果我实现类更换了,那你也不需要更改注入的类了吧?
这是接口降低耦合度的一种。至少我换一个实现类或者改n次实现类的名字也不会让调用方去跟着改,因为我接口没变,你只是调我接口。
第二种场景:扩展
为什么会出现接口?不就是为了弥补单继承的缺陷吗?接口可以多继承啊。假如我要实现的service接口它要扩展呢?继承多个接口就好了。那么我要实现的这个接口不是又多了很多方法?我的接口只需要再继承多个接口就多了很多方法。
或者假如我没有实现接口,我这个类给别人注入使用,后来我这个类另一个人也想用同样的那个方法,但是功能逻辑和原来的不一样了,而且原来的功能别人也还在调用,那么我就得改原来的逻辑。但是如果使用接口就可以再写一个实现类,另一个人用新的实现类的方法就行了,不用修改原来的方法。这符合开闭原则(对扩展开放,对修改关闭)。
这是接口利于扩展的一种。
第三种场景:设计模式
很多设计模式中会用到接口,抽象类。我们的service层完全可以使用多种设计模式来设计,提高代码复用,健壮性。比如最常用的模板模式,张三和李四的功能
第四种场景:rpc远程调用
springaop使用了两种代理模式:jdk动态代理和cglib动态代理,他们可以来回切换。人家spring都没抛弃jdk动态代理肯定是有他的可取之处的,毕竟jdk基于反射生成代理类很快。spring都鼓励我们使用接口呢。不只是spring,dubbo,springCloud以及其他框架都鼓励我们使用接口呢。
远程调用,接口暴露了,但是却不暴露实现细节。
------------------------------------------------------------------------
引用:
service层使用接口的好处 - super超人 - 博客园