关于C#项目中 服务层使用接口的问题

发布于:2025-05-10 ⋅ 阅读:(17) ⋅ 点赞:(0)

问题背景:在重构一个项目中发现存在 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 动态代理的工作流程
  1. Spring 发现 UserServiceImpl 实现了 UserService 接口。

  2. 创建代理对象,代理的是 UserService 接口,而非 UserServiceImpl 类本身。

  3. 当调用 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超人 - 博客园

分层设计中的Service层:接口实现的必要性与C#示例-CSDN博客

C#基于接口设计三层架构Unity篇 - 缥缈的尘埃 - 博客园


网站公告

今日签到

点亮在社区的每一天
去签到