service层中的接口是干什么用的?

请问各位在使用spring开发web应用时,有没有遇到service层中的接口被多实现的场景?接口-单实现类写吐了不知道有什么作用

interface IUserService{
    User getUserByUserName(String username);
}

p c UserServiceAAA implements IUserService{
    @Override
    public User getUserByUserName(String username){
        return userDao.去分公司的库里查();
    }
}

p c UserServiceBBB implements IUserService{
    @Override
    public User getUserByUserName(String username){
        return rpc.调用当地劳动局的接口("https://peoplexxx.com");
    }
}

即使遇到多个实现类,这个接口被注入的地方也需要策略来决定用哪个实现类吧,直接在选择策略的地方决定注入哪个service bean不就行了吗?我在选择这两个实现类的时候还是要用spring @Qualifier注解决定注哪一个,那改类名和改@Qualifier注解值好像也没多大区别吧,甚至还更简洁了,那这个接口存在的意义在哪里?

p c UserController{
    @Qualifier("serviceAAA")
    @Autowired
    private IUserService userService;

    @Qualifier("serviceBBB")
    @Autowired
    private IUserService userService;

    //===============
    
    @Autowired
    private UserServiceAAA userServiceAAA;

    @Autowired
    private UserServiceBBB userServiceBBB;
}
阅读 1.9k
avatarAI BotBETA

在Spring框架中,Service层中的接口通常用于定义业务逻辑操作的契约。接口定义了应该执行哪些操作,但不关心这些操作的具体实现。这种设计方式有几个主要的好处:

  1. 解耦:接口允许将业务逻辑的实现与调用这些逻辑的代码分离。这使得代码更易于维护和测试,因为你可以独立地更改实现,而不需要修改使用这些实现的代码。
  2. 多态性:通过接口,你可以实现同一接口的不同实现类,然后在运行时根据需要选择使用哪个实现。这在处理不同的数据源、使用不同的算法或根据不同的配置更改行为时非常有用。
  3. 易于替换:由于Service层的接口和实现类之间的松耦合,你可以轻松地替换一个实现类为另一个,而无需更改上层代码。这对于引入新的业务逻辑或优化现有逻辑非常有用。

关于你提到的多实现类场景,接口允许你以统一的方式处理多个实现,即使它们背后的逻辑可能完全不同。在选择使用哪个实现时,确实需要某种策略。这可以通过多种方式实现,例如使用@Qualifier注解、配置文件、注解或条件化bean的创建等。

对于你的示例代码,有几点需要注意:

  • 你不能在同一个类中使用两个相同的字段名(userService),即使它们具有不同的@Qualifier值。你需要为每个字段使用不同的名称。
  • 使用@Qualifier注解是一种明确指定要注入哪个bean的方式,它有助于解决当Spring容器中存在多个相同类型的bean时的歧义。
  • 尽管直接注入具体的实现类(如UserServiceAAAUserServiceBBB)在技术上是可行的,但这会破坏接口的初衷,即解耦和灵活性。如果你这样做,你就失去了通过接口实现多态性的能力,并且每次需要更改实现时都需要修改代码。

接口存在的意义在于提供抽象和灵活性,允许你在不修改上层代码的情况下更改或扩展业务逻辑。虽然在某些简单场景下可能看起来没有直接区别,但随着项目复杂性的增加,使用接口将变得更加重要。

总结来说,Service层中的接口是为了定义业务逻辑的契约、实现解耦和多态性,从而提高代码的灵活性和可维护性。在多实现类场景中,接口允许你以统一的方式处理不同的实现,并通过策略决定使用哪个实现。

3 个回答

其实这种写法本质上就是 策略模式
可以看一下这个教程
策略模式|菜鸟教程
我觉得优点就是你可以保证每个实现类一定都有相同的行为,并且在新增一个实现类也不会影响到原来的实现类(开闭原则)
缺点当然也有,灵活度被限制了,你写起来确实会比较烦(新增一种实现类型就要多写一个实现类),属于是开发的时候麻烦维护的时候容易

即使遇到多个实现类,这个接口被注入的地方也需要策略来决定用哪个实现类吧,直接在选择策略的地方决定注入哪个service bean不就行了吗?我在选择这两个实现类的时候还是要用spring @Qualifier注解决定注哪一个,那改类名和改@Qualifier注解值好像也没多大区别吧

你题目里说的情况是假定你在写代码时就已经确定了要使用哪种策略的情况,那么两种写法没什么区别。我提一种情形你看看,比如我要根据不同的客人来决定给他使用什么优惠策略(这是程序运行时动态传入的参数),如果使用接口实现类写法:

@Autowired
private ApplicationContext context;

public double calculateTotalWithDiscount(double orderAmount, String discountStrategyBeanName) {
    DiscountStrategy strategy = (DiscountStrategy)context.getBean(discountStrategyBeanName);
    OrderContext orderContext = new OrderContext(strategy);
    return orderAmount - orderContext.calculateOrderDiscount(orderAmount);
}

如果不使用接口实现类写法:

@Autowired
private ApplicationContext context;

public double calculateTotalWithDiscount(double orderAmount, String discountStrategyBeanName) {
  if(discountStrategyBeanName.equals("A策略")){
      实现类A aService = context.getBean(实现类A);
      return orderAmount - aService.calculateOrderDiscount(orderAmount);//这里方法名可能还不一样
  }else if(discountStrategyBeanName.equals("B策略")){
      实现类B bService = context.getBean(实现类B)
      return orderAmount - bService.calculateOrderDiscount(orderAmount);//这里方法名可能还不一样
  }else if(discountStrategyBeanName.equals("C策略")){
      实现类C cService = context.getBean(实现类C);
      return orderAmount - cService.calculateOrderDiscount(orderAmount);//这里方法名可能还不一样
  }//...以后每次新增一个实现类都得在这里加一段
}

当然两种都可以使用,那么以后每多一种策略呢?使用接口的话你需要做的只是写一个实现了接口的类就行了,其他什么都不需要改,不使用接口的话就要多写一个类和多加一种处理,策略多起来以后很容易遗忘也麻烦。另外我觉得还有一个好处是在IDE中可以通过点击某个接口快速找到相关的实现类,这样你就能很快知道有哪些类是处理相关业务的。
如果你不涉及我说的情形我觉得倒也没什么区别,无非是美观度和写代码的时间多那么一点点(可能维护也是)。总之你觉得怎么写方便怎么维护容易你就怎么写。

这种设计方式几个常规的说法:

  1. 解耦:接口允许将业务逻辑的实现与调用这些逻辑的代码分离。这使得代码更易于维护和测试,因为你可以独立地更改实现,而不需要修改使用这些实现的代码。
  2. 多态性:通过接口,你可以实现同一接口的不同实现类,然后在运行时根据需要选择使用哪个实现。这在处理不同的数据源、使用不同的算法或根据不同的配置更改行为时非常有用。
  3. 易于替换:由于Service层的接口和实现类之间的松耦合,你可以轻松地替换一个实现类为另一个,而无需更改上层代码。这对于引入新的业务逻辑或优化现有逻辑非常有用。

实际上你的疑惑是对的,大多数时候用不上。
但是有个好处就是:保持统一结构,作为一个统一的模板代码,可以根据表结构一键生成基础结构代码。开发人员只需要填空就可以。

结果就是,项目有统一结构更好懂,也保留所谓的扩展性,也没让你多敲代码,那么多数情况就这么做了。

有些时候调用接口时,会调用所有的实现,并不关心具体有哪些实现,这时优势就出来了。
针对接口多实现的问题,可以采用以下几种解决方案:

3.1 抽象工厂模式
抽象工厂模式是一种创建型设计模式,它提供了一种创建一系列相关或相互依赖对象的接口。通过使用抽象工厂模式,可以将接口多实现的问题转化为工厂方法模式的问题。具体来说,可以创建一个抽象工厂类,该类负责创建和管理所有实现类的对象。这样,当需要调用接口方法时,只需通过抽象工厂类获取相应的实现类对象即可。

3.2 策略模式
策略模式是一种行为型设计模式,它定义了一系列算法,并将每个算法封装在一个具有共同接口的单独类中。通过使用策略模式,可以将接口多实现的问题转化为策略选择的问题。具体来说,可以创建一个策略接口和多个策略实现类。然后,在Service层中,可以根据需要选择合适的策略实现类来调用接口方法。

3.3 适配器模式
适配器模式是一种结构型设计模式,它用于将一个类的接口转换为另一个客户端期望的接口。通过使用适配器模式,可以将接口多实现的问题转化为适配器的选择问题。具体来说,可以创建一个适配器类,该类实现了Service层的接口,并持有一个实现类的引用。这样,当需要调用接口方法时,只需通过适配器类即可。

  1. 实践案例
    下面通过一个实践案例来演示如何使用上述解决方案来解决接口多实现的问题。

4.1 案例背景
假设有一个名为UserService的Service层接口,该接口负责处理用户相关的业务逻辑。现在,需要为该接口添加一个新的方法deleteUserById,该方法需要删除指定ID的用户。由于项目已经存在多个实现了UserService接口的类(如AdminUserService、GuestUserService等),需要确保这些类能够正确地调用新添加的方法。

4.2 解决方案一:抽象工厂模式
使用抽象工厂模式来解决这个问题。具体步骤如下:

创建一个抽象工厂类UserServiceFactory,该类负责创建和管理所有实现了UserService接口的类的对象。
在UserServiceFactory中,添加一个方法createAdminUserService(),用于创建AdminUserService对象;添加一个方法createGuestUserService(),用于创建GuestUserService对象。
在UserServiceFactory中,添加一个方法getUserService(),该方法根据传入的参数返回相应的UserService对象。例如,如果传入参数表示管理员用户,则返回AdminUserService对象;如果传入参数表示普通用户,则返回GuestUserService对象。
在需要调用deleteUserById方法的地方,通过调用UserServiceFactory.getUserService()方法获取相应的UserService对象,然后调用该对象的deleteUserById()方法即可。
4.3 解决方案二:策略模式
接下来,使用策略模式来解决这个问题。具体步骤如下:

创建一个策略接口DeleteUserStrategy,该接口包含一个方法deleteUserById()。
创建多个实现了DeleteUserStrategy接口的类(如AdminDeleteUserStrategy、GuestDeleteUserStrategy等),分别用于处理管理员用户和普通用户的删除操作。
在需要调用deleteUserById()方法的地方,根据当前用户的类型选择合适的策略实现类(如AdminDeleteUserStrategy、GuestDeleteUserStrategy等),然后调用该策略对象的deleteUserById()方法即可。
4.4 解决方案三:适配器模式
使用适配器模式来解决这个问题。具体步骤如下:

创建一个适配器类AdminUserAdapter和GuestUserAdapter,分别实现了UserService接口。这两个适配器类分别持有一个实现了相应业务逻辑的类的引用(如AdminUserServiceImpl、GuestUserServiceImpl等)。
在需要调用新添加的deleteUserById()方法的地方,根据当前用户的类型选择合适的适配器类(如AdminUserAdapter、GuestUserAdapter等),然后调用该适配器对象的相应方法即可。

宣传栏