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

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 【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;
    @Override
    public 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;
  @Override
  public 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);
  }
}

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


相关文章
|
3月前
|
Java 测试技术 微服务
最新技术栈下 Java 面试高频技术点实操指南详解
本指南结合最新Java技术趋势,涵盖微服务(Spring Cloud Alibaba)、响应式编程(Spring WebFlux)、容器化部署(Docker+Kubernetes)、函数式编程、性能优化及测试等核心领域。通过具体实现步骤与示例代码,深入讲解服务注册发现、配置中心、熔断限流、响应式数据库访问、JVM调优等内容。适合备战Java面试,提升实操能力,助力技术进阶。资源链接:[https://pan.quark.cn/s/14fcf913bae6](https://pan.quark.cn/s/14fcf913bae6)
159 25
|
3月前
|
缓存 Java 关系型数据库
2025 年最新华为 Java 面试题及答案,全方位打造面试宝典
Java面试高频考点与实践指南(150字摘要) 本文系统梳理了Java面试核心考点,包括Java基础(数据类型、面向对象特性、常用类使用)、并发编程(线程机制、锁原理、并发容器)、JVM(内存模型、GC算法、类加载机制)、Spring框架(IoC/AOP、Bean生命周期、事务管理)、数据库(MySQL引擎、事务隔离、索引优化)及分布式(CAP理论、ID生成、Redis缓存)。同时提供华为级实战代码,涵盖Spring Cloud Alibaba微服务、Sentinel限流、Seata分布式事务,以及完整的D
166 1
|
2月前
|
缓存 Java API
Java 面试实操指南与最新技术结合的实战攻略
本指南涵盖Java 17+新特性、Spring Boot 3微服务、响应式编程、容器化部署与数据缓存实操,结合代码案例解析高频面试技术点,助你掌握最新Java技术栈,提升实战能力,轻松应对Java中高级岗位面试。
322 0
|
3月前
|
存储 安全 Java
常见 JAVA 集合面试题整理 自用版持续更新
这是一份详尽的Java集合面试题总结,涵盖ArrayList与LinkedList、HashMap与HashTable、HashSet与TreeSet的区别,以及ConcurrentHashMap的实现原理。内容从底层数据结构、性能特点到应用场景逐一剖析,并提供代码示例便于理解。此外,还介绍了如何遍历HashMap和HashTable。无论是初学者还是进阶开发者,都能从中受益。代码资源可从[链接](https://pan.quark.cn/s/14fcf913bae6)获取。
182 3
|
3月前
|
存储 安全 Java
2025 最新史上最全 Java 面试题独家整理带详细答案及解析
本文从Java基础、面向对象、多线程与并发等方面详细解析常见面试题及答案,并结合实际应用帮助理解。内容涵盖基本数据类型、自动装箱拆箱、String类区别,面向对象三大特性(封装、继承、多态),线程创建与安全问题解决方法,以及集合框架如ArrayList与LinkedList的对比和HashMap工作原理。适合准备面试或深入学习Java的开发者参考。附代码获取链接:[点此下载](https://pan.quark.cn/s/14fcf913bae6)。
983 48
|
3月前
|
算法 架构师 Java
Java 开发岗及 java 架构师百度校招历年经典面试题汇总
以下是百度校招Java岗位面试题精选摘要(150字): Java开发岗重点关注集合类、并发和系统设计。HashMap线程安全可通过Collections.synchronizedMap()或ConcurrentHashMap实现,后者采用分段锁提升并发性能。负载均衡算法包括轮询、加权轮询和最少连接数,一致性哈希可均匀分布请求。Redis持久化有RDB(快照恢复快)和AOF(日志更安全)两种方式。架构师岗涉及JMM内存模型、happens-before原则和无锁数据结构(基于CAS)。
96 5
|
3月前
|
Java API 微服务
2025 年 Java 校招面试全攻略:从面试心得看 Java 岗位求职技巧
《2025年Java校招最新技术要点与实操指南》 本文梳理了2025年Java校招的核心技术栈,并提供了可直接运行的代码实例。重点技术包括: Java 17+新特性(Record类、Sealed类等) Spring Boot 3+WebFlux响应式编程 微服务架构与Spring Cloud组件 Docker容器化部署 Redis缓存集成 OpenAI API调用 通过实际代码演示了如何应用这些技术,如Java 17的Record类简化POJO、WebFlux构建响应式API、Docker容器化部署。
125 5
|
3月前
|
缓存 NoSQL Java
Java Redis 面试题集锦 常见高频面试题目及解析
本文总结了Redis在Java中的核心面试题,包括数据类型操作、单线程高性能原理、键过期策略及分布式锁实现等关键内容。通过Jedis代码示例展示了String、List等数据类型的操作方法,讲解了惰性删除和定期删除相结合的过期策略,并提供了Spring Boot配置Redis过期时间的方案。文章还探讨了缓存穿透、雪崩等问题解决方案,以及基于Redis的分布式锁实现,帮助开发者全面掌握Redis在Java应用中的实践要点。
171 6
|
3月前
|
安全 Java API
2025 年 Java 校招面试常见问题及详细答案汇总
本资料涵盖Java校招常见面试题,包括Java基础、并发编程、JVM、Spring框架、分布式与微服务等核心知识点,并提供详细解析与实操代码,助力2025校招备战。
158 1
|
3月前
|
算法 Java 微服务
2025 年 Java 面试宝典社招春招秋招实操全方位攻略
2025年Java面试宝典涵盖核心技术及最新趋势,分为四大板块:1. Java基础:深入数据类型、多态等特性,结合学生信息管理等实例;2. JVM核心:解析内存模型与GC算法,附多线程转账等场景应用;3. 高并发方案:详解synchronized与线程池配置,提供Web服务器优化案例;4. Spring生态:剖析IoC/AOP原理,演示微服务架构实现。特别新增Java 17+特性实操,包括Record类、密封接口等语法糖,整合Spring Boot 3、响应式编程及云原生技术,通过订单状态机、API网关配置。
221 1