每天认识一个设计模式-策略模式:解耦算法的艺术

一、前言
在 Java 开发领域,设计模式对于构建稳健且高效的软件系统具有至关重要的意义。当开发者面对复杂多变的业务逻辑时,设计模式能够切实有效地提升代码的可扩展性与可维护性,进而提高代码质量,优化开发流程。
在众多设计模式之中,策略模式独具特色。其主要解决的是在多种相似算法存在时,因条件分支所导致的代码臃肿问题。比如在代码中存在大量的 if - else 或 switch - case 语句时,会致使代码冗长繁杂、可读性差,维护与扩展的难度显著增大。每增加一个条件分支,都极有可能引入新的错误,从而进一步加剧代码的复杂性。
策略模式为上述难题提供了行之有效的解决方案。它将不同的算法封装为独立的策略类,使得算法的变更与使用该算法的客户端相互独立。如此一来,在不改变现有代码结构的前提下,便能便捷地添加或替换算法。希望大家能从本文中汲取新的灵感与知识,使策略模式成为优化代码的有力工具。
二、策略模式的模式原型设计说明
在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。策略模式定义了一系列算法或策略,并将每个算法封装在独立的类中,使得它们可以互相替换。通过使用策略模式,可以在运行时根据需要选择不同的算法,而不需要修改客户端代码。
在深入了解策略模式的应用之前,我们先来剖析一下它的核心结构和设计原则。策略模式作为一种行为型设计模式,其结构简洁而精妙,蕴含着深刻的设计思想。
2.1策略模式的结构解释
策略模式主要由三个核心角色构成,它们相互协作,共同实现了算法的封装与灵活切换。下面我们通过 UML 图来直观地了解它们之间的关系:

✅Context(环境类):Context 是策略模式的使用者,它持有一个 Strategy 接口的引用,就像是一个 “指挥官”,负责调用具体策略的方法。通过setStrategy方法,它可以在运行时动态地切换所使用的策略,就如同指挥官可以根据战场形势随时更换作战策略一样。
✅Strategy(抽象策略):这是一个抽象的接口或类,它定义了一系列算法的公共接口,也就是定义了所有具体策略类必须实现的方法。它为不同的具体策略提供了一个统一的契约,确保了它们在行为上的一致性,类似于制定了一套通用的作战规范。
✅ConcreteStrategy(具体策略):这些是实现了 Strategy 接口的具体类,每个类都封装了一种特定的算法或行为。它们是策略模式的具体执行者,就像不同的作战小队,各自拥有独特的作战方式,但都遵循着统一的作战规范。
2.2策略模式的设计原则
策略模式不仅仅是一种结构上的设计,更是对一系列设计原则的完美践行,这些原则赋予了策略模式强大的生命力和灵活性。
✅开闭原则:开闭原则是设计模式中的重要原则之一,它要求软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。在策略模式中,当我们需要添加新的策略时,只需要创建一个新的具体策略类并实现 Strategy 接口即可,而不需要修改 Context 类的代码。
✅组合优于继承:策略模式通过组合的方式,将具体策略对象聚合到 Context 中,而不是通过继承来实现行为的扩展。这种方式避免了继承带来的复杂性和局限性,使得代码更加灵活和可维护。
✅运行时多态:策略模式利用 Java 的多态特性,在运行时根据实际情况动态地选择和切换策略。这意味着我们可以在程序运行过程中,根据不同的条件选择不同的算法来执行,就像在一场比赛中,运动员可以根据比赛情况选择不同的战术。这种动态性使得系统能够更好地适应变化的需求,提高了系统的灵活性和适应性。
三、策略模式的场景实践
在了解了策略模式的理论基础后,让我们深入探讨一下它在实际开发中的应用场景以及在一些知名开源框架中的体现,同时也会给出一些在开发中使用策略模式的建议。
3.1典型应用场景
策略模式在各种业务场景中都有着广泛的应用,它能够有效地解决许多实际问题,提高代码的可维护性和可扩展性。
✅算法变体管理:在支付系统中,我们常常需要支持多种支付方式,如微信支付、支付宝支付、银联支付等。每种支付方式都有其独特的处理逻辑,包括支付请求的发送、支付结果的验证等。使用策略模式,我们可以将这些不同的支付方式封装成各自的策略类,实现支付逻辑的解耦。当需要添加新的支付方式时,只需创建一个新的策略类,而无需修改原有的支付逻辑代码。
在风控系统中,风控规则引擎需要根据不同的业务场景和风险等级执行不同的风控规则。例如,对于新用户和老用户,可能需要采用不同的风险评估策略;对于不同类型的交易,也可能有不同的风控规则。通过策略模式,我们可以将这些不同的风控规则封装成策略类,使得规则的管理和扩展更加方便。
✅动态逻辑切换:电商平台在促销活动期间,会根据不同的促销策略对商品价格进行调整,如满减、折扣、赠品等。这些促销策略在不同的时间段或针对不同的商品可能会有所不同。使用策略模式,我们可以将每种促销策略封装成一个策略类,在运行时根据具体的促销活动选择相应的策略。这样,当促销活动发生变化时,我们只需切换策略类,而无需修改大量的业务逻辑代码。
以视频平台为例,根据用户的网络状况,平台可以动态选择不同的视频播放策略,如流畅模式、高清模式、超清模式等。当网络状况良好时,选择高清或超清模式以提供更好的观看体验;当网络状况不佳时,切换到流畅模式以保证视频的正常播放。这种动态切换播放策略的实现,就可以借助策略模式来完成。
✅代码坏味道治理:在代码中,如果存在大量的多重条件判断语句(if - else 或 switch - case),这往往是代码设计不够合理的表现,会导致代码的可读性和可维护性变差。策略模式可以有效地消除这些多重条件判断,将每个条件分支的逻辑封装成一个策略类。
例如在一个订单处理系统中,根据订单的状态(待支付、已支付、已发货、已完成等)执行不同的操作,如果使用传统的 if - else 语句,代码会变得冗长且难以维护。而使用策略模式,我们可以为每个订单状态创建一个对应的策略类,在订单状态发生变化时,直接调用相应的策略类来处理,使代码结构更加清晰,易于扩展和维护。
这里我们可以看到的一些应用场景其实之前聊到的设计模式也有提到过,但是策略主要体现自业务内部的逻辑封装,所以策略模式也是与其他设计模式协同开发中一个非常常见的模式。
3.2开源框架中的策略模式
策略模式在许多开源框架中都有广泛的应用,这些框架通过策略模式实现了功能的灵活性和可扩展性,为开发者提供了强大的工具。
✅Java Comparator 接口:在 Java 中,Comparator接口是策略模式的一个典型应用。Comparator接口定义了一个compare方法,用于比较两个对象。通过实现Comparator接口,我们可以定义不同的比较策略。
当我们在对一个List进行排序时,可以传入不同的Comparator实现类,来实现升序、降序或其他自定义的排序方式。这种方式使得排序算法的选择和使用更加灵活,符合策略模式的思想。
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class ComparatorExample {
public static void main(String[] args) {
List<Integer> numbers = new ArrayList<>();
numbers.add(5);
numbers.add(3);
numbers.add(8);
numbers.add(1);
// 使用升序比较策略
Comparator<Integer> ascendingComparator = (o1, o2) -> o1 - o2;
Collections.sort(numbers, ascendingComparator);
System.out.println("升序排序结果: " + numbers);
// 使用降序比较策略
Comparator<Integer> descendingComparator = (o1, o2) -> o2 - o1;
Collections.sort(numbers, descendingComparator);
System.out.println("降序排序结果: " + numbers);
}
}
✅Spring Resource 接口:Spring 框架中的Resource接口也是策略模式的应用。Resource接口提供了统一的资源访问方式,而具体的资源加载策略则由不同的实现类来完成,如UrlResource用于访问网络资源,ClassPathResource用于访问类路径下的资源,FileSystemResource用于访问文件系统资源等。通过这种方式,Spring 框架实现了资源访问策略的灵活切换,满足了不同场景下的资源加载需求。
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import java.io.IOException;
import java.net.MalformedURLException;
public class SpringResourceExample {
public static void main(String[] args) {
try {
// 使用ClassPathResource加载类路径下的资源
Resource classPathResource = new ClassPathResource("example.txt");
System.out.println("类路径资源内容: " + new String(classPathResource.getInputStream().readAllBytes()));
// 使用UrlResource加载网络资源
Resource urlResource = new UrlResource("https://example.com");
System.out.println("网络资源内容: " + new String(urlResource.getInputStream().readAllBytes()));
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
✅MyBatis Executor:MyBatis 框架中的Executor接口是策略模式的又一体现。Executor接口定义了 SQL 语句的执行策略,包括SimpleExecutor(简单执行器)、BatchExecutor(批量执行器)、ReuseExecutor(重用执行器)等不同的实现类。通过配置不同的Executor实现类,我们可以在不同的场景下选择最优的执行策略,提高数据库操作的效率。
<configuration>
<settings>
<!-- 配置使用BatchExecutor -->
<setting name="defaultExecutorType" value="BATCH"/>
</settings>
</configuration>
3.3策略模式的开发建议
在我们日常开发工作中中,使用策略模式可以有效地提高代码的质量和可维护性,但在应用过程中也需要注意一些问题,以避免过度设计和不必要的复杂性。
⚠️避免过度设计:虽然策略模式能够解决许多复杂的问题,但对于简单的条件分支逻辑,直接使用 if - else 或 switch - case 语句可能更加简洁明了。过度使用策略模式会导致代码结构变得复杂,增加开发和维护的成本。因此,在决定是否使用策略模式时,需要综合考虑业务逻辑的复杂程度和变化频率。
⚠️枚举 + 策略组合:为了更好地管理和使用策略类,可以将策略模式与枚举类型相结合。通过枚举类型来定义不同的策略标识,使得策略的选择和切换更加直观和安全。同时,枚举类型还可以提供一些额外的方法和属性,方便对策略进行管理和配置。
public enum PaymentStrategyEnum {
WECHAT_PAY("微信支付", new WeChatPayStrategy()),
ALIPAY_PAY("支付宝支付", new AliPayStrategy());
private final String name;
private final PaymentStrategy strategy;
PaymentStrategyEnum(String name, PaymentStrategy strategy) {
this.name = name;
this.strategy = strategy;
}
public PaymentStrategy getStrategy() {
return strategy;
}
}
⚠️粒度控制:在设计策略类时,需要注意控制策略类的粒度。每个策略类应该具有单一的职责,只负责实现一种具体的算法或行为。如果策略类的职责过于复杂,会导致代码的可维护性下降,也违背了策略模式的设计初衷。同时,策略类的粒度也不能过于细化,否则会导致策略类的数量过多,增加管理和维护的难度。
四、策略模式的实际业务场景搭建
在当今数字化服务的大环境下,为用户提供个性化服务是提升用户体验和满意度的关键。现在我们通过一个智能客服系统和策略角色了解一下策略模式在中的实际应用。
构建的智能客服系统,核心功能是根据用户等级(普通用户或 VIP 用户)生成不同的回复话术,以此实现更加个性化的服务。对于 VIP 用户,为给予他们更尊贵的体验,回复时使用尊称,并强调优先处理他们的问题。而对于普通用户,则采用标准的回复话术。这种根据用户等级动态切换回复策略的需求,恰好是策略模式的典型应用场景。
首先定义用户实体类(User),用于表示系统中的用户,包含用户名和是否为 VIP 用户的标识。
// 用户实体类
public class User {
private String name;
private boolean vip;
public User(String name, boolean vip) {
this.name = name;
this.vip = vip;
}
public String getName() {
return name;
}
public boolean isVip() {
return vip;
}
}
封装一下用户请求实体(UserRequest),封装用户的请求信息,包括发起请求的用户和具体问题。
// 用户请求实体类
public class UserRequest {
private User user;
private String question;
public UserRequest(User user, String question) {
this.user = user;
this.question = question;
}
public User user() {
return user;
}
public String question() {
return question;
}
}
完成回复策略接口(ResponseStrategy),定义生成回复话术的方法,所有具体的回复策略类都要实现这个接口。
// 策略接口
public interface ResponseStrategy {
String generateResponse(User user, String question);
}
然后针对于VIP用户和普通用户,去实现具体的策略。VIP 用户策略实现类(VipResponseStrategy)实现ResponseStrategy接口,针对 VIP 用户生成特定回复话术。
// VIP用户策略实现类
@Component("vipStrategy")
public class VipResponseStrategy implements ResponseStrategy {
@Override
public String generateResponse(User user, String question) {
return String.format("尊贵的%s,您的问题'%s'将优先处理", user.getName(), question);
}
}
普通用户策略实现类(NormalResponseStrategy)同样实现ResponseStrategy接口,为普通用户生成标准回复话术。
// 普通用户策略实现类
@Component("normalStrategy")
public class NormalResponseStrategy implements ResponseStrategy {
@Override
public String generateResponse(User user, String question) {
return String.format("您好,%s,您的问题'%s'已收到,我们会尽快处理", user.getName(), question);
}
}
根据策略模式搭建的角色我们创建策略上下文,这里主要是回复策略上下文类(ResponseContext),负责管理和执行不同的回复策略。通过依赖注入,获取所有实现了ResponseStrategy接口的 Bean,并存储在strategyMap中。在executeStrategy方法中,根据传入的策略键(strategyKey)从strategyMap中获取对应的策略,并执行生成回复话术的操作。如果指定的策略不存在,则使用默认策略。
// 策略上下文
@Service
public class ResponseContext {
private final Map<String, ResponseStrategy> strategyMap;
@Autowired
public ResponseContext(Map<String, ResponseStrategy> strategies) {
this.strategyMap = strategies;
}
public String executeStrategy(String strategyKey, User user, String question) {
return strategyMap.getOrDefault(strategyKey, defaultStrategy())
.generateResponse(user, question);
}
private ResponseStrategy defaultStrategy() {
return (user, question) -> "您好,您的问题已收到,我们会尽快处理";
}
}
最后创建一下客户控制器类(CustomerController)接收用户的请求完成接口,根据用户是否为 VIP 确定使用的策略键,然后调用ResponseContext的executeStrategy方法生成回复,并返回给用户。
// 控制器类
@RestController
public class CustomerController {
@Autowired
private ResponseContext responseContext;
@PostMapping("/query")
public ResponseEntity<String> handleQuery(@RequestBody UserRequest request) {
String strategyKey = request.user().isVip()? "vipStrategy" : "normalStrategy";
return ResponseEntity.ok(responseContext.executeStrategy(strategyKey, request.user(), request.question()));
}
}
这样通过一个简单的策略模式搭建,我们可以将不同的回复逻辑分散到各自独立的策略类中,每个策略类只负责一种特定的回复策略。当业务需求发生变化,比如修改 VIP 用户的回复话术时,只需修改VipResponseStrategy类,而不会影响到其他策略类和整个系统的其他部分。这样使得代码的维护更加容易,降低了因代码修改导致的风险。
如果未来业务发展需要增加新的用户等级,如 “超级 VIP 用户”,只需要创建一个新的策略类实现ResponseStrategy接口,然后在系统中注册该策略类即可。不需要对现有的大量代码进行大规模修改,轻松实现系统功能的扩展。
五、总结
策略模式的核心在于将算法的实现与使用场景分离,使得业务逻辑专注于自身的职责,而不必关心具体算法的细节。这种解耦不仅提高了代码的可读性,还使得系统的维护和扩展变得更加容易。
细心的朋友可能会发现策略模式和状态模式在结构上有相似之处,但正如我上面说到的,它们的侧重点不同。
状态模式更侧重于根据对象的内部状态来改变其行为,状态的切换通常是自动的,由对象自身的状态变化触发。而策略模式则强调根据外部条件或用户选择来动态地切换算法或行为,策略的选择是由客户端主动决定的。
拿电商订单系统来说,订单的状态(如待支付、已支付、已发货等)变化会导致订单处理行为的改变,这是状态模式的应用场景;而在支付环节,用户可以选择不同的支付方式(如微信支付、支付宝支付等),这则是策略模式的体现。
另外为了更好地管理策略实例的创建和生命周期,在实际应用上我们可以将策略模式与工厂模式相结合。工厂模式负责创建具体的策略对象,客户端只需向工厂请求所需的策略,而无需关心策略对象的创建细节。这样可以进一步解耦客户端与策略实现,提高代码的可维护性和可扩展性。
其他的设计模式也可根据我们自己的业务场景去协同使用,这样可以让设计模式在实际开发场景中更好的应用!
更多推荐
所有评论(0)