【Java】和面试官谈策略模式

你还在大篇幅的使用if…else吗?

举个例子:比如你们有一个订单系统,用户在平时下单和在双11的时候下单的时候逻辑是不一样的,可能双11下单就涉及到一些优惠之类的,这个时候你怎么做,应该有好多同学是这样做的,前端传一个参数来区分普通下单和双11下单,后台用if else来判断两个分支来处理逻辑,那这样好像也没啥问题,但是后面到双12了,老板说双12优惠力度又不一样了,你又得加一个else ,然后还需要修改之前已经测试没问题的代码, 这样你这个代码块还需要重新测试而且整体的代码简洁度也不美观了

那有什么最优的办法呢?那就是使用策略模式

本篇文章将通过策略模式的概念和优缺点以及几个完整的示例来讲解如何在工作和学习当中将策略模式融入的你的业务当中

一、什么是策略模式

策略模式是一种设计模式,它定义了一系列算法,并将每一个算法封装起来,使它们可以相互替换。这种模式的主要目的是解决在有多种算法相似的情况下,使用“if…else”所带来的复杂和难以维护的问题。

1.1 策略模式的优点

策略模式的优点有:

  1. 提供了管理相关的算法族的办法:策略模式的等级结构定义了一个算法或行为族,恰当使用继承可以把公共的代码移到抽象策略类中,从而避免重复的代码。
  2. 提供了可以替换继承关系的办法:如果不使用策略模式,那么使用算法的环境类就可能会有一些子类,每一个子类提供一种不同的算法。但是,这样一来算法的使用就和算法本身混在一起,不符合“单一职责原则”,决定使用哪一种算法的逻辑和该算法本身混合在一起,从而不可能再独立演化;而且使用继承无法实现算法或行为在程序运行时的动态切换。
  3. 使用策略模式可以避免多重条件选择语句:使用策略模式可以避免在多个地方使用“if-else”或“switch-case”语句来根据不同的条件选择不同的算法或行为。
  4. 提供了对“开闭原则”的完美支持:用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。
  5. 简化了单元测试:因为每个算法都有自己的类,可以通过自己的接口单独测试。

总之,策略模式是一种通过封装算法和行为来简化复杂系统设计的模式,它允许在运行时根据需要动态地选择不同的策略实现。

1.2策略模式的使用场景

策略模式适用于以下场景:

  1. 针对同一类型问题的多种处理方式,仅仅是具体行为有差别时。
  2. 需要安全地封装多种同一类型的操作时。
  3. 出现同一抽象类有多个子类,而又需要使用 if-else 或者 switch-case 来选择具体子类时。
  4. 多个类只有在算法或行为上稍有不同的情况下。
  5. 算法需要自由切换的情况下。
  6. 需要屏蔽算法规则对客户端造成的影响时,例如减少 if…else 语句时。
  7. 当一个类有多种行为方式时,可以使用策略模式来动态地选择行为方式。
1.3 策略模式和工厂模式的区别

有些同学经常把策略模式和工厂模式弄混,那我们也来看看策略模式和工厂模式的区别:

  1. 用途:工厂模式的主要作用是创建对象,而策略模式的主要作用是让一个对象在许多行为中选择一种行为。
  2. 关注点:工厂模式关注的是对象的创建,而策略模式关注的是行为的封装。
  3. 传参:工厂模式的传参是一个类型,而策略模式的传参是一个对象。
  4. 适用场景:工厂模式主要应用在多数据库选择、类库文件加载等场景中,而策略模式则适用于策略的切换与扩展,定义策略族,分别封装起来,让他们之间可以相互替换。

总结来说,工厂模式和策略模式虽然相似,但它们的设计理念和适用场景有所不同。工厂模式注重创建对象,而策略模式注重行为的封装和算法的独立性。因此,在使用时需要根据具体需求选择合适的模式。

二、策略模式的简单示例

下面以一个简单的代码示例来演示策略模式:

// 定义一个接口
public interface Strategy {  void execute();  
}  // 两个实现类分别实现这个接口
public class StrategyA implements Strategy {  @Override  public void execute() {  // 第一段逻辑  System.out.println("执行第一段逻辑");  }  
}  public class StrategyB implements Strategy {  @Override  public void execute() {  // 第二段逻辑  System.out.println("执行第二段逻辑");  }  
}  public class Context {  private Strategy strategy;  public Context(Strategy strategy) {  this.strategy = strategy;  }  public void setStrategy(Strategy strategy) {  this.strategy = strategy;  }  public void executeStrategy() {  strategy.execute();  }  
}  public class Main {  public static void main(String[] args) {  Context context = new Context(new StrategyA()); // 设置执行第一段逻辑的策略  context.executeStrategy(); // 执行策略,输出 "执行第一段逻辑"  context.setStrategy(new StrategyB()); // 更换为执行第二段逻辑的策略  context.executeStrategy(); // 执行策略,输出 "执行第二段逻辑"  }  
}

在上面的示例中,我们定义了一个Strategy接口,其中包含一个execute方法,用于执行相应的逻辑。然后,我们创建了两个实现了Strategy接口的类StrategyAStrategyB,分别表示第一段逻辑和第二段逻辑。

我们还定义了一个Context类,它持有一个Strategy对象,并提供了一个executeStrategy方法来执行相应的策略。客户端代码可以通过设置不同的策略对象来决定执行哪一段逻辑。在示例中,我们创建了一个Context对象,并使用StrategyA作为初始策略来执行第一段逻辑。然后,我们通过调用setStrategy方法更换为StrategyB来执行第二段逻辑。

三、策略模式和业务的结合

相信好多同学光看上面的代码可能还不知道怎么将策略模式应用到自己的代码当中,那下面就给出一个个示例(本人实际上过生产的项目)

ps: 以下代码都是我模拟的生产代码(因为生产的不能公开哦)

3.1 登录认证

登录认证想必大家都不陌生,那我们的项目可能就会对应几种认证方式:可能会是账号密码、验证码、扫码等等

下面我就实现两种认证方式来演示策略模式:

3.1.1 首先创建一个认证的接口
/*** 统一认证接口** @author jiagang*/
public interface ITokenGranter {/*** 用户认证** @param tokenParameter 授权参数* @return UserInfo*/UserInfo grant(TokenParameter tokenParameter);}
3.1.2 分别创建账号密码登录和验证码登录的类

创建两个类分别实现ITokenGranter接口

账号密码实现:

@Component
@AllArgsConstructor
public class PasswordTokenGranter implements ITokenGranter {public static final String GRANT_TYPE = "password";private IUserService userClient;@Overridepublic UserInfo grant(TokenParameter tokenParameter) {// 下面逻辑简单模拟了就String account = tokenParameter.getAccount();String password = tokenParameter.getPassword();// 通过账号和密码(解密后)来查询用户 -- userClient为调用用户服务的能力return userClient.userInfo(account, DigestUtil.encrypt(password));}}

验证码实现:

@Component
@AllArgsConstructor
public class CaptchaTokenGranter implements ITokenGranter {public static final String GRANT_TYPE = "captcha";private IUserService userClient;private RedisUtil redisUtil;@Overridepublic UserInfo grant(TokenParameter tokenParameter) {HttpServletRequest request = WebUtil.getRequest();String key = request.getHeader(TokenUtil.CAPTCHA_HEADER_KEY);String code = request.getHeader(TokenUtil.CAPTCHA_HEADER_CODE);// 获取验证码String redisCode = String.valueOf(redisUtil.get(CacheNames.CAPTCHA_KEY + key));// 判断验证码if (code == null || !StringUtil.equalsIgnoreCase(redisCode, code)) {throw new ServiceException(TokenUtil.CAPTCHA_NOT_CORRECT);}// 下面逻辑简单模拟了就String account = tokenParameter.getAccount();String password = tokenParameter.getPassword();// 通过账号和密码(解密后)来查询用户 -- userClient为调用用户服务的能力return userClient.userInfo(account, DigestUtil.encrypt(password));}}
3.1.3 创建一个Builder

这个类的作用是通过授权方式(grantType)来确定使用哪个实现类

@AllArgsConstructor
public class TokenGranterBuilder {/*** TokenGranter缓存池*/private static final Map<String, ITokenGranter> GRANTER_POOL = new ConcurrentHashMap<>();static {GRANTER_POOL.put(PasswordTokenGranter.GRANT_TYPE, SpringUtil.getBean(PasswordTokenGranter.class));GRANTER_POOL.put(CaptchaTokenGranter.GRANT_TYPE, SpringUtil.getBean(CaptchaTokenGranter.class));}/*** 获取TokenGranter** @param grantType 授权类型* @return ITokenGranter*/public static ITokenGranter getGranter(String grantType) {// password 为默认授权类型ITokenGranter tokenGranter = GRANTER_POOL.get(toStr(grantType, PasswordTokenGranter.GRANT_TYPE));if (tokenGranter == null) {throw new SecureException("no grantType was found");} else {return tokenGranter;}}public static String toStr(Object str, String defaultValue) {return null == str ? defaultValue : String.valueOf(str);}}
3.1.4 controller调用
@RestController
@AllArgsConstructor
@RequestMapping("/auth")
public class AuthController {private IAuthService authService;private RedisUtil redisUtil;/*** 登陆认证*/@PostMapping("login")public R<UserInfo> token(@RequestBody TokenParameter tokenParameter) {// 通过类型获取不同实现类的认证ITokenGranter granter = TokenGranterBuilder.getGranter(tokenParameter.getGrantType());// 调用接口UserInfo userInfo = granter.grant(tokenParameter);if (userInfo == null || userInfo.getUser() == null || userInfo.getUser().getId() == null) {return R.fail(TokenUtil.USER_NOT_FOUND);}return R.data(userInfo);}}

内容结束啦,最后送大家一句话 白驹过隙,沧海桑田

文章持续更新,可以关注下方公众号或者微信搜一搜「 最后一支迷迭香 」获取项目源码,第一时间阅读,获取更完整的链路资料。

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

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

相关文章

摄影-基础知识

光圈&#xff0c;快门&#xff0c;感光度决定了一张相片的受光程度 光圈 瞳孔 快门 约等于 眼皮(但是实际上并不是&#xff0c;更像镜头盖) 感光度 视网膜上的感光能力 光圈越大 景深越大&#xff0c;也就是画面越模糊 快门时间越短&#xff0c;越能抓住某个瞬间 快门时间…

如何在Linux系统中安装Redis

原本Redis官网提供了Windows和Linux两个版本&#xff0c;但从 2011-12-29 以后不再更新Windows版本&#xff08;https://github.com/dmajkic/redis/downloads&#xff09;&#xff0c;加之企业生产环境通常使用Linux系统&#xff0c;所以这里在Linux系统中演示如何安装Redis。 …

梳理Langchain-Chatchat-UI接口文档

在 Langchain-Chatchat v0.1.17 版本及以前是有前后端分离的 Vue 项目的&#xff0c;但是 v0.2.0 后就没有了。所以本文使用的是 Langchain-Chatchat v0.1.17 版本中的 Vue 项目。经过一番折腾终于将 Langchain-Chatchat v0.1.17 版本前端 Vue 接口和 Langchain-Chatchat v0.2.…

数据预处理:标准化和归一化

标准化和归一化简介 1、数据预处理概述2、数据标准化3、数据归一化4、标准化和归一化怎么选1、数据预处理概述 在选择了合适模型的前提下,机器学习可谓是“训练台上3分钟,数据数量和质量台下10年功”。数据的收集与准备是机器学习中的重要一步,是构建一个好的预测模型大厦的…

jenkins+pytest+allure

jenkinspytestallure allure下载地址 Releases allure-framework/allure2 GitHub allure环境变量配置 allure --version 查看版本(确定是否配置完成) python安装allure插件 pip install allure-pytest pytest的运行指令 pytest -sv test_demo.py 开发完毕后将代码上传到…

深入理解@Resource与@Autowired:用法与区别解析

Resource&#xff1a; Resource 是Java EE提供的注解&#xff0c;也可以在Spring中使用。它是按照名称进行注入的&#xff0c;默认通过属性名&#xff08;通常是类名的小驼峰命名方式&#xff09;或者name属性来匹配。如果找不到符合名称的bean&#xff0c;则会抛出异常。在使…

轻量应用服务器阿里云61元、腾讯云62元,你选哪个?

阿里云和腾讯云又降价了&#xff0c;刚刚说完阿里云87元和腾讯云88元&#xff0c;又降级了&#xff0c;阿里云2核2G3M轻量应用服务器61元一年&#xff0c;腾讯云轻量2核2G3M服务器62元一年&#xff0c;你选哪个&#xff1f;阿里云不限制月流量&#xff0c;腾讯云限制200GB月流量…

2024 年 9 款简单好用的 Windows 分区管理器软件

了解适用于 Windows 11 和 Windows 7 的 Windows 分区管理器的概念。本教程还列出了分区管理器软件&#xff1a; 购买新电脑&#xff1f;担心磁盘存储空间不足&#xff1f;你听说过分区吗&#xff1f;如果没有&#xff0c;这篇文章就是为你准备的。 在本文中&#xff0c;我们…

Linux:apache优化(7)—— 访问控制

作用&#xff1a;为apache服务提供的页面设置客户端访问权限&#xff0c;为某个组或者某个用户加密访问&#xff1b; /usr/local/httpd/bin/htpasswd -c /usr/local/httpd/conf/htpasswd tarro1 #添加admin用户&#xff0c;可以在两个路径中间添加-c是新建文件删除原文件&#…

SPI通信协议:串行外设接口的精髓

SPI通信协议&#xff1a;串行外设接口的精髓 SPI&#xff08;Serial Peripheral Interface&#xff09;通信协议是一种常见且广泛应用于串行通信的标准&#xff0c;特别适用于连接微控制器与外围设备。本文将深入介绍SPI通信协议的基本原理、工作方式、硬件连接、应用领域以及…

Linux系统驱动要如何学习

1.你将获得&#xff1a; 快速上手 Linux 操作系统&#xff1b; 掌握Linux 内核工作原理&#xff1b; 掌握Linux 内核调试手段&#xff1b; 掌握复杂驱动&#xff1a;USB、PCIE、V4L2等 这门课程旨在为你打开Linux内核驱动的大门&#xff0c;让你在探索Linux内核的旅程中获得前…

C++常用工具函数-1

1、转为16进制 unsigned long temp 16&#xff1b; std::cout<< "temp2"<<std::setbase(16)<< temp << std::endl; 2、数组转指针操作 unsigned char W[4*8*15]; // the expanded key unsigned int * Wb reinterpret_cast<unsigned…

QT的信号与槽

QT的信号与槽 文章目录 QT的信号与槽前言一、QT 打印"hello QT"的dome二、信号和槽机制&#xff1f;二、信号与槽的用法1、QT5的方式1. 无参的信号与槽的dome2.带参的信号与槽dome 2、QT4的方式3、C11的语法 Lambda表达式1、函数对象参数2、操作符重载函数参数3、可修…

LAYABOX:2024新年寄语

2024新年寄语 过去的一年&#xff0c;尽管许多行业面临严峻挑战和发展压力&#xff0c;小游戏领域却逆势上扬&#xff0c;年产值首次突破400亿元大关&#xff0c;众多优质小游戏企业收获颇丰。 对此&#xff0c;祝福大家&#xff0c;2024一定更好&#xff01; 过去的一年&#…

伺服电机的控制模式

一、伺服电机基本的控制模式 伺服电机的基本控制模式有位置模式、速度模式、转矩模式 二、位置模式 位置模式对速度和位置都有严格的控制&#xff0c;通过控制发送脉冲的频率&#xff0c;来确定电机的转动杆速度大小&#xff0c;通过控制发送脉冲的个数来确定转动的角度。位置…

CMake入门教程【基础篇】CMake编译平台

文章目录 简介Visual Studio支持示例 其他编译器和生成器支持MinGW示例 IDE集成Eclipse示例 实验性和特殊平台支持总结 简介 CMake是一个非常强大的跨平台自动化构建工具&#xff0c;它支持生成多种类型的项目文件&#xff0c;覆盖了广泛的开发环境和编译器。在这篇博客中&…

33--反射

1、反射(Reflection)的概念 1.1 反射的出现背景 Java程序中&#xff0c;所有的对象都有两种类型&#xff1a;编译时类型和运行时类型&#xff0c;而很多时候对象的编译时类型和运行时类型不一致。 Object obj new String("hello"); obj.getClass(); 例如&#xf…

【话题】ChatGPT等大语言模型为什么没有智能2

我们接着上一次的讨论&#xff0c;继续探索大模型的存在的问题。正巧CSDN最近在搞文章活动&#xff0c;我们来看看大模型“幻觉”。当然&#xff0c;本文可能有很多我自己的“幻觉”&#xff0c;欢迎批评指正。如果这么说的话&#xff0c;其实很容易得出一个小结论——大模型如…

DS1302N的时钟逻辑

时钟也是一个实时的串口&#xff0c;也是很简单的&#xff0c;不过要注意以下的要点&#xff1a; 要点&#xff1a; &#xff08;1&#xff09;里面有很多数据&#xff0c;所以需要定义一个结构体变量&#xff0c;将其中的数据写进去。 &#xff08;2&#xff09;写进去的数…

.Net Core 防御XSS攻击

网络安全攻击方式有很多种&#xff0c;其中包括XSS攻击、SQL注入攻击、URL篡改等。那么XSS攻击到底是什么?XSS攻击有哪几种类型? XSS攻击又称为跨站脚本&#xff0c;XSS的重点不在于跨站点&#xff0c;而是在于脚本的执行。XSS是一种经常出现在Web应用程序中的计算机安全漏洞…