面试官:Spring 用到了哪些设计模式?

news2024/11/25 3:05:56

文章目录

  • 前言
  • 一、策略模式
  • 二、工厂方法模式
  • 三、Builder模式
  • 总结


前言

关于设计模式,如果使用得当,将会使我们的代码更加简洁,并且更具扩展性。本文主要讲解Spring中如何使用策略模式,工厂方法模式以及Builder模式。


一、策略模式

关于策略模式的使用方式,在Spring中其实比较简单,从本质上讲,策略模式就是一个接口下有多个实现类,而每种实现类会处理某一种情况。

我们以发奖励为例进行讲解,比如我们在抽奖系统中,有多种奖励方式可供选择,比如积分,虚拟币和现金等。在存储时,我们必然会使用一个类似于type的字段用于表征这几种发放奖励的,那么这里我们就可以使用多态的方式进行奖励的发放。比如我们抽象出一个PrizeSender的接口,其声明如下:

public interface PrizeSender {

  /**
   * 用于判断当前实例是否支持当前奖励的发放
   */
  boolean support(SendPrizeRequest request);

  /**
   * 发放奖励
   */
  void sendPrize(SendPrizeRequest request);

}

该接口中主要有两个方法:support()和sendPrize(),其中support()方法主要用于判断各个子类是否支持当前类型数据的处理,而sendPrize()则主要是用于进行具体的业务处理的,比如这里奖励的发放。下面就是我们三种不同类型的奖励发放的具体代码:

// 积分发放
@Component
public class PointSender implements PrizeSender {

  @Override
  public boolean support(SendPrizeRequest request) {
    return request.getPrizeType() == PrizeTypeEnum.POINT;
  }

  @Override
  public void sendPrize(SendPrizeRequest request) {
    System.out.println("发放积分");
  }
}
// 虚拟币发放
@Component
public class VirtualCurrencySender implements PrizeSender {

  @Override
  public boolean support(SendPrizeRequest request) {
    return PrizeTypeEnum.VIRTUAL_CURRENCY == request.getPrizeType();
  }

  @Override
  public void sendPrize(SendPrizeRequest request) {
    System.out.println("发放虚拟币");
  }
}
// 现金发放
@Component
public class CashSender implements PrizeSender {

  @Override
  public boolean support(SendPrizeRequest request) {
    return PrizeTypeEnum.CASH == request.getPrizeType();
  }

  @Override
  public void sendPrize(SendPrizeRequest request) {
    System.out.println("发放现金");
  }
}

这里可以看到,在每种子类型中,我们只需要在support()方法中通过request的某个参数来控制当前request是否是当前实例能够处理的类型,如果是,则外层的控制逻辑就会将request交给当前实例进行处理。关于这个类的设计,有几个点需要注意:

  • 使用@Component注解对当前类进行标注,将其声明为Spring容器所管理的一个bean;
  • 声明一个返回boolean值的类似于support()的方法,通过这个方法来控制当前实例是否为处理目标request的实例;
  • 声明一个类似于sendPrize()的方法用于处理业务逻辑,当然根据各个业务的不同声明的方法名肯定是不同的,这里只是一个对统一的业务处理的抽象;
  • 无论是support()方法还是sendPrize()方法,都需要传一个对象进行,而不是简简单单的基本类型的变量,这样做的好处是后续如果要在Request中新增字段,那么就不需要修改接口的定义和已经实现的各个子类的逻辑;

二、工厂方法模式

上面我们讲解了如何使用Spring来声明一个策略模式,那么如何为不同的业务逻辑来注入不同的bean呢,或者说外层的控制逻辑是什么样的,这里我们就可以使用工厂方法模式了。

所谓的工厂方法模式,就是定义一个工厂方法,通过传入的参数,返回某个实例,然后通过该实例来处理后续的业务逻辑。一般的,工厂方法的返回值类型是一个接口类型,而选择具体子类实例的逻辑则封装到了工厂方法中了。通过这种方式,来将外层调用逻辑与具体的子类的获取逻辑进行分离。如下图展示了工厂方法模式的一个示意图:

在这里插入图片描述
可以看到,工厂方法将具体实例的选择进行了封装,而客户端,也就是我们的调用方只需要调用工厂的具体方法获取到具体的事例即可,而不需要管具体的实例实现是什么。

上面我们讲解了Spring中是如何使用策略模式声明处理逻辑的,而没有讲如何选择具体的策略,这里我们就可以使用工厂方法模式。

如下是我们声明的一个PrizeSenderFactory:

@Component
public class PrizeSenderFactory {

  @Autowired
  private List<PrizeSender> prizeSenders;

  public PrizeSender getPrizeSender(SendPrizeRequest request) {
    for (PrizeSender prizeSender : prizeSenders) {
      if (prizeSender.support(request)) {
        return prizeSender;
      }
    }

    throw new UnsupportedOperationException("unsupported request: " + request);
  }
}

这里我们声明一个了一个工厂方法getPrizeSender(),其入参就是SendPrizeRequest,而返回值是某个实现了PrizeSender接口的实例,可以看到,通过这种方式,我们将具体的选择方式下移到了具体的子类中的,因为当前实现了PrizeSender的bean是否支持当前request的处理,是由具体的子类实现的。

在该工厂方法中,我们也没有任何与具体子类相关的逻辑,也就是说,该类实际上是可以动态检测新加入的子类实例的。这主要是通过Spring的自动注入来实现的,主要是因为我们这里注入的是一个List,也就是说,如果有新的PrizeSender的子类实例,只要其是Spring所管理的,那么都会被注入到这里来。下面就是我们编写的一段用于测试的代码来模拟调用方的调用:

@Service
public class ApplicationService {

  @Autowired
  private PrizeSenderFactory prizeSenderFactory;

  public void mockedClient() {
    SendPrizeRequest request = new SendPrizeRequest();
    request.setPrizeType(PrizeTypeEnum.POINT);  // 这里的request一般是根据数据库或外部调用来生成的
    PrizeSender prizeSender = prizeSenderFactory.getPrizeSender(request);
    prizeSender.sendPrize(request);
  }
}

在客户端代码中,首先通过PrizeSenderFactory获取一个PrizeSender实例,然后通过其sendPrize()方法发放具体的奖励,通过这种方式,将具体的奖励发放逻辑与客户端调用进行了解耦。而且根据前面的讲解,我们也知道,如果新增了一种奖励方式,我们只需要声明一个新的实现了PrizeSender的bean即可,而不需要对现有代码进行任何修改。

三、Builder模式

关于Builder模式,我想使用过lombok的同学肯定会说builder模式非常的简单,只需要在某个bean上使用@Builder注解进行声明即可,lombok可以自动帮我们将其声明为一个Builder的bean。关于这种使用方式,本人不置可否,不过就我的理解,这里主要有两个点我们需要理解:

1、Builder模式就其名称而言,是一个构建者,我更倾向于将其理解为通过一定的参数,通过一定的业务逻辑来最终生成某个对象。如果仅仅只是使用lombok的这种方式,其本质上也还是创建了一个简单的bean,这个与通过getter和setter方式构建一个bean是没有什么大的区别的;

2、在Spring框架中,使用设计模式最大的问题在于如果在各个模式bean中能够注入Spring的bean,如果能够注入,那么将大大的扩展其使用方式。因为我们就可以真的实现通过传入的简单的几个参数,然后结合Spring注入的bean进行一定的处理后,以构造出我们所需要的某个bean。显然,这是lombok所无法实现的;

关于Builder模式,我们可以以前面奖励发放的SendPrizeRequest的构造为例进行讲解。在构造request对象的时候,必然是通过前台传如的某些参数来经过一定的处理,最后生成一个request对象。那么我们就可以使用Builder模式来构建一个SendPrizeRequest。

这里假设根据前台调用,我们能够获取到prizeId和userId,那么我们就可以创建一个如下的SendPrizeRequest:

public class SendPrizeRequest {

  private final PrizeTypeEnum prizeType;
  private final int amount;
  private final String userId;

  public SendPrizeRequest(PrizeTypeEnum prizeType, int amount, String userId) {
    this.prizeType = prizeType;
    this.amount = amount;
    this.userId = userId;
  }

  @Component
  @Scope("prototype")
  public static class Builder {

    @Autowired
    PrizeService prizeService;

    private int prizeId;
    private String userId;

    public Builder prizeId(int prizeId) {
      this.prizeId = prizeId;
      return this;
    }

    public Builder userId(String userId) {
      this.userId = userId;
      return this;
    }

    public SendPrizeRequest build() {
      Prize prize = prizeService.findById(prizeId);
      return new SendPrizeRequest(prize.getPrizeType(), prize.getAmount(), userId);
    }
  }

  public PrizeTypeEnum getPrizeType() {
    return prizeType;
  }

  public int getAmount() {
    return amount;
  }

  public String getUserId() {
    return userId;
  }
}

这里就是使用Spring维护一个Builder模式的示例,具体的 维护方式就是在Builder类上使用@Component和@Scope注解来标注该Builder类,这样我们就可以在Builder类中注入我们所需要的实例来进行一定的业务处理了。关于该模式,这里有几点需要说明:

  • 在Builder类上必须使用@Scope注解来标注该实例为prototype类型,因为很明显,我们这里的Builder实例是有状态的,无法被多线程共享;
  • 在Builder.build()方法中,我们可以通过传入的参数和注入的bean来进行一定的业务处理,从而得到构建一个SendPrizeRequest所需要的参数;
  • Builder类必须使用static修饰,因为在Java中,如果内部类不用static修饰,那么该类的实例必须依赖于外部类的一个实例,而我们这里本质上是希望通过内部类实例来构建外部类实例,也就是说内部类实例存在的时候,外部类实例是还不存在的,因而这里必须使用static修饰;
  • 根据标准的Builder模式的使用方式,外部类的各个参数都必须使用final修饰,然后只需要为其声明getter方法即可。

上面我们展示了如何使用Spring的方式来声明一个Builder模式的类,那么我们该如何进行使用呢,如下是我们的一个使用示例:

@Service
public class ApplicationService {

  @Autowired
  private PrizeSenderFactory prizeSenderFactory;

  @Autowired
  private ApplicationContext context;

  public void mockedClient() {
    SendPrizeRequest request = newPrizeSendRequestBuilder()
        .prizeId(1)
        .userId("u4352234")
        .build();

    PrizeSender prizeSender = prizeSenderFactory.getPrizeSender(request);
    prizeSender.sendPrize(request);
  }

  public Builder newPrizeSendRequestBuilder() {
    return context.getBean(Builder.class);
  }
}

上述代码中,我们主要要看一下newPrizeSendRequestBuilder()方法,在Spring中,如果一个类是多例类型,也即使用@Scope(“prototype”)进行了标注,那么每次获取该bean的时候就必须使用ApplicationContext.getBean()方法获取一个新的实例,至于具体的原因,读者可查阅相关文档。

我们这里就是通过一个单独的方法来创建一个Builder对象,然后通过流式来为其设置prizeId和userId等参数,最后通过build()方法构建得到了一个SendPrizeRequest实例,通过该实例来进行后续的奖励发放。


总结

本文主要通过一个奖励发放的示例来对Spring中如何使用工厂方法模式,策略模式和Builder模式的方式进行讲解,并且着重强调了实现各个模式时我们所需要注意的点。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1179672.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数字滤波器之高通滤波器设计

文章来源地址&#xff1a;https://www.yii666.com/blog/393376.html 通过在Z平面放置零极点的来设计数字滤波器 要求&#xff1a;设计一款高通滤波器&#xff0c;用在音频信号处理过程中&#xff0c;滤掉100Hz以下的信号。 实现方法&#xff1a;通过在Z平面放置零极点的来设…

多测师肖sir_高级金牌讲师__git讲解

git 一、git的介绍 &#xff08;一&#xff09;git的理论介绍&#xff08;版本控制工具&#xff09; git &#xff0c;目前世界上最先级的分布式版本控制系统&#xff0c;可以有效&#xff0c;高速的处理从很小到非常大的项目版本管理。 git是linus torvalds 为了帮助管理linu…

什么是柴油发电机组负载测试

柴油发电机组负载测试是对柴油发电机组在不同负载条件下进行性能和稳定性测试的过程&#xff0c;负载测试旨在评估发电机组在实际运行中的工作能力和性能表现&#xff0c;以确保其能够在负载变化时稳定可靠地提供电力。在负载测试中&#xff0c;需要确定测试负载的大小和类型。…

idea使用gradle教程 (idea gradle springboot)2024

这里白眉大叔&#xff0c;写一下我工作时候idea怎么使用gradle的实战步骤吧 ----windows 环境----------- 1-本机安装gradle 环境 &#xff08;1&#xff09;下载gradle Gradle需要JDK的支持&#xff0c;安装Gradle之前需要提前安装JDK8及以上版本 https://downloads.gra…

MES生产执行系统源码

MES生产执行系统源码 MES系统介绍 MES系统着重解决生产过程管控、防错防呆、产质量追溯、设备运行等相关管理目标&#xff0c;具体如下&#xff1a; &#xff08;1&#xff09;全面集成。承上启下&#xff0c;完成公司所有与MES系统链接的信息化系统 &#xff08;如ERP、PLM…

BSP-STM32移植FreeRTOS

在stm32裸机工程中的Middlewares目录添加freeRtos源码 在裸机工程中的main中调用freertos接口

最新版Office2024安装教程

一. 介绍&#xff1a;Office版本都是每三年发布一个版本&#xff0c;从Office 2007、2010、2013、2016、2019&#xff0c;2021到现在的2024。 二. 下载&#xff1a; http://dt1.8tupian.net/2/29913a54b1000.pg3三. 安装教程&#xff1a; 1.用到的软件是开源的脚本&#xff0c…

菜鸟打印组件系列-vue3快速接入

文章目录 前言1. 相关名词或语句2. CAINIAO打印组件能力3. 安装与下载4. vue3集成步骤4.1 使用pina 创建websoket相关处理的模块。4.2 创建本地自定义模板&#xff08;要打印的模板以及样式&#xff09;4.3 结合el-table &#xff0c;实现批量打印 总结 前言 文章主要记录不注…

unity Holoens2开发,使用Vuforia识别实体或图片 触发交互

建议&#xff1a;先看官方文档 我使用的utniy 版本&#xff1a;2020.3.48f1c1 官方建议&#xff1a;混合现实工具包简介 - 设置项目并使用手势交互 - Training | Microsoft Learn 配置了正确工具的 Windows 10 或 11 电脑Windows 10 SDK 10.0.18362.0 或更高版本安装了 Unit…

界面控件DevExpress WPF PDF Viewer,更快实现应用的PDF文档浏览

DevExpress WPF PDF Viewer控件可以轻松地直接在Windows应用程序中显示PDF文档&#xff0c;而无需在最终用户的机器上安装外部PDF查看器。 P.S&#xff1a;DevExpress WPF拥有120个控件和库&#xff0c;将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpress…

怎么恢复永久删除的文件?这3个方法很实用!

“非常着急&#xff01;永久删除的文件还有机会恢复吗&#xff1f;大家怎么恢复永久删除的文件的呀&#xff1f;快帮帮我吧&#xff01;” 在数字化时代&#xff0c;文件的意外永久删除可能是一个常见问题.有些朋友可能不小心永久删除了文件之后会感到很绝望&#xff0c;但是别…

Python翻页代码示例

1. 首先&#xff0c;我们需要一个依赖于Ruby的库来帮助我们进行网络请求&#xff0c;这个库叫做Open-uri。我们可以使用require open-uri来引入这个库。 2. 接下来&#xff0c;我们需要定义一个变量来存储信息。在这个例子中&#xff0c;代理信息是proxy_host: 和proxy_port: &…

HarmonyOS应用开发

引言 本章将深入探讨 HarmonyOS 应用开发的关键方面&#xff0c;包括应用的生命周期、数据存储和网络访问。了解这些内容对于创建功能丰富、高效的 HarmonyOS 应用至关重要。 目录 HarmonyOS 应用的生命周期HarmonyOS 应用的数据存储HarmonyOS 应用的网络访问总结 1. Harmo…

蓝桥杯双周赛算法心得——串门(双链表数组+双dfs)

大家好&#xff0c;我是晴天学长&#xff0c;树和dfs的结合&#xff0c;其邻接表的存图方法也很重要。需要的小伙伴可以关注支持一下哦&#xff01;后续会继续更新的。&#x1f4aa;&#x1f4aa;&#x1f4aa; 1) .串门 2) .算法思路 串门&#xff08;怎么存图很关键&#xf…

【Linux】-模拟实现一个缓冲区

&#x1f496;作者&#xff1a;小树苗渴望变成参天大树&#x1f388; &#x1f389;作者宣言&#xff1a;认真写好每一篇博客&#x1f4a4; &#x1f38a;作者gitee:gitee✨ &#x1f49e;作者专栏&#xff1a;C语言,数据结构初阶,Linux,C 动态规划算法&#x1f384; 如 果 你 …

ACmix:卷积和self-attention的结合,YOLOv5改进之ACmix

目录 一、ACmix理论部分 二、代码 三、YOLOv5改进 ACC3 一、ACmix理论部分 论文地址:2111.14556.pdf (arxi

Git简介和安装

一&#xff0c;Git简介 Git 是一个分布式版本控制工具&#xff0c;通常用来对软件开发过程中的源代码文件进行管理。通过Git 仓库来存储和管理这些文件&#xff0c;Git 仓库分为两种&#xff1a; 本地仓库&#xff1a;开发人员自己电脑上的 Git 仓库 远程仓库&#xff1a;远程…

centos部署nginx集群

部署nginx集群&#xff0c;一台nginx出故障&#xff0c;另外的nginx能直接使用&#xff08;这里用两台做实验&#xff09; 规划&#xff1a;143.1.26.212是主&#xff0c;143.1.26.213是从&#xff0c;143.1.26.214是VIP&#xff08;vip是客户访问的ip&#xff09; 小白教程&…

浅谈剩余电流动作继电器在电动伸缩门的应用

摘 要&#xff1a;随着时代的发展&#xff0c;越来越多的小区、厂区、园区和学校等场所的大门安装了电动伸缩门&#xff0c;几乎可以说随处可见。电动伸缩门是一种长期在户外使用的设备&#xff0c;工作电压为220 V&#xff08;过去也有380 V&#xff09;&#xff0c;其电机是处…

文件批量移动自动创建文件夹及生成编号重命名,让整理更轻松

在我们日常的生活和工作中&#xff0c;文件的整理和管理是至关重要的一项任务。有时候&#xff0c;我们需要将大量的文件转移到不同的文件夹中&#xff0c;并对它们进行重命名。手动执行这些任务不仅耗时&#xff0c;而且容易出错。云炫文件管理器能够轻松实现文件的批量移动、…