网站首页 > java教程 正文
Java 策略模式是一种常见的行为型模式,它可以将算法的实现逻辑与客户端代码分离,从而提高代码的灵活性和可维护性。本文将详细介绍策略模式的原理、使用场景以及代码实现。
一、原理
策略模式属于行为型设计模式,它定义了算法族(一组算法),并将各个算法封装起来,使它们之间可以互相替换,从而使算法的变化独立于使用算法的客户端。策略模式通过使用不同的策略来达到相同的目的,从而使得整个系统更加灵活、可扩展和易于维护。
策略模式的核心思想就是将算法的实现逻辑从客户端代码中解耦出来,将其封装到一个独立的类中,让客户端只需要知道如何调用这个类即可。这个独立的类被称之为策略类,它通常会定义一个公共的接口或抽象类,用于描述算法族的方法签名。
在策略模式中,客户端通过一个 Context(上下文)对象来使用其中的算法,Context 对象会持有一个具体的策略对象,根据传入的参数选择相应的策略进行调用,从而实现算法的动态切换。
策略模式的优点是可以提高程序的可扩展性和可维护性,同时也可以让程序更加灵活。因为不同的算法都是独立的,所以可以方便的增加或删除算法,并且可以很容易的修改算法的实现方式。
二、使用场景
策略模式在很多场景下都可以使用,比如表单验证、计算方式、支付方式等等。只要存在不同的算法实现,都可以使用策略模式来进行抽象和封装。
总的来说策略模式适用于以下场景:
1、系统中有多个类或对象可以完成某项操作,但它们的算法或行为不同,并且需要在运行时动态地选择其中的一种。
2、一个系统需要动态地在几种算法中选择一种,而这些算法可以作为系统的一部分来进行封装。这样,系统就可以很方便地切换算法或者增加新的算法,而不必因为算法的变化而修改整个系统代码。
3、需要将算法的实现细节对客户端代码隐藏起来,使得客户端只需要知道如何调用算法,而不必了解算法的实现细节。
4、当一个类有多个行为或算法,而这些行为或算法在不同的场景下会发生变化,使用策略模式可以避免使用大量的 if-else 语句进行判断,增强了程序的可读性和可维护性。
三、示例代码讲解实现过程
下面我们通过一个简单的例子来说明如何使用策略模式。
假设有一个商场销售系统,系统中有多种优惠策略可以选择,例如不打折、满减、打折等等。现在需要实现这些优惠策略的动态切换,以便于满足不同用户、不同商品的需求。
首先,我们需要定义一个抽象类 DiscountStrategy,其中包含一个 calculateDiscount() 抽象方法,这个方法用于计算具体的优惠金额。
public abstract class DiscountStrategy {
public abstract double calculateDiscount(double totalPrice);
}
然后,我们可以定义不同的具体的 DiscountStrategy 子类,例如 NoDiscountStrategy(不打折)、FullReductionStrategy(满减优惠)和 DiscountStrategy(打折优惠)等等,这些子类分别实现 calculateDiscount() 方法:
// 不打折
public class NoDiscountStrategy extends DiscountStrategy {
@Override
public double calculateDiscount(double totalPrice) {
return 0;
}
}
// 满减优惠
public class FullReductionStrategy extends DiscountStrategy {
private double fullPrice; // 满多少元才能享受优惠
private double reducePrice; // 优惠的金额
public FullReductionStrategy(double fullPrice, double reducePrice) {
this.fullPrice = fullPrice;
this.reducePrice = reducePrice;
}
@Override
public double calculateDiscount(double totalPrice) {
if (totalPrice >= fullPrice) {
return reducePrice;
} else {
return 0;
}
}
}
// 打折优惠
public class DiscountStrategy extends DiscountStrategy {
private double discount; // 折扣率
public DiscountStrategy(double discount) {
this.discount = discount;
}
@Override
public double calculateDiscount(double totalPrice) {
return totalPrice * discount;
}
}
接下来,我们需要定义一个 Context 对象来封装具体的 DiscountStrategy 子类,该对象可以根据传入的参数选择相应的策略进行调用:
public class DiscountContext {
private DiscountStrategy strategy;
public void setStrategy(DiscountStrategy strategy) {
this.strategy = strategy;
}
public double calculateDiscount(double totalPrice) {
if (strategy != null) {
return strategy.calculateDiscount(totalPrice);
} else {
return 0;
}
}
}
最后,我们可以在客户端代码中使用 DiscountContext 对象来计算具体的优惠金额:
public static void main(String[] args) {
DiscountContext context = new DiscountContext();
// 如果不打折
context.setStrategy(new NoDiscountStrategy());
double totalPrice1 = 100;
double discount1 = context.calculateDiscount(totalPrice1); // 返回 0
// 如果满 100 元就减 10 元
context.setStrategy(new FullReductionStrategy(100, 10));
double totalPrice2 = 120;
double discount2 = context.calculateDiscount(totalPrice2); // 返回 10
// 如果打 8 折
context.setStrategy(new DiscountStrategy(0.8));
double totalPrice3 = 200;
double discount3 = context.calculateDiscount(totalPrice3); // 返回 40
}
在上面的示例的 main 方法中,我们使用同一个 context 对象,分三次使用三种算法计算出结果,通过 setStrategy() 方法来动态切换三种算法。这就是一个简单的策略模式实现过程。
四、Spring Boot 中使用的适配器模式
在Spring Boot源码中,有多个模块使用了策略模式,例如:
1、Spring Boot Actuator
Spring Boot Actuator 模块提供了一组监控和管理应用程序的端点(endpoints),其中就使用了策略模式来实现不同类型端点的处理。具体来说,它定义了一个抽象父类 AbstractEndpoint,以及多个子类,每个子类都实现了不同的处理逻辑。在 Actuator 端点处理时,会根据请求路径选择不同的 Endpoint 进行处理,这样就避免了代码中的大量 if-else 语句。
2、Spring Boot 配置文件的加载过程
在 Spring Boot 中,配置文件可以按照不同的格式进行存储,例如 properties、yml 等等。为了支持这些不同的格式,Spring Boot 通过使用策略模式来实现对配置文件的加载和解析。
具体来说,在加载和解析配置文件的过程中,Spring Boot 会定义一个 PropertySourceLoader 接口,其中包含一个 load() 方法,用于加载和解析具体的配置文件。然后,Spring Boot 会定义多个具体的 PropertySourceLoader 实现类,每个实现类用于加载和解析一种特定的文件格式,例如 PropertiesPropertySourceLoader(用于加载 properties 文件)和 YamlPropertySourceLoader(用于加载 yml 文件)等等。这些具体的实现类都会实现 load() 方法,并根据传入的参数类型选择相应的解析方式进行处理,从而实现了对不同格式配置文件的支持。
源代码如下:
public interface PropertySourceLoader {
// 返回一个字符串数组,表示当前加载器支持的配置文件扩展名。
// 例如,XmlPropertySourceLoader 的扩展名为 "xml"。
String[] getFileExtensions();
// 用于加载配置信息,并返回一个 PropertySource 对象列表。
// 其中,name 表示配置文件名称,resource 表示配置文件资源。
// 在方法内部,它可以根据不同的配置文件类型,使用不同的方式去加载和解析配置信息。
List<PropertySource<?>> load(String name, Resource resource) throws IOException;
}
Spring Boot 默认实现的两种配置配置文件实现类 PropertiesPropertySourceLoader(用于加载 properties 文件)和 YamlPropertySourceLoader(用于加载 yml 文件)
public class YamlPropertySourceLoader implements PropertySourceLoader {
// 返回支持的文件扩展名
@Override
public String[] getFileExtensions() {
return new String[] { "yml", "yaml" };
}
// 加载并解析 Yaml 配置文件
@Override
public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
// 检查 snakeyaml 是否在类路径中
if (!ClassUtils.isPresent("org.yaml.snakeyaml.Yaml", null)) {
throw new IllegalStateException(
"Attempted to load " + name + " but snakeyaml was not found on the classpath");
}
// 使用 OriginTrackedYamlLoader 解析 Yaml 文件
List<Map<String, Object>> loaded = new OriginTrackedYamlLoader(resource).load();
// 如果解析出来的结果为空,则返回一个空列表
if (loaded.isEmpty()) {
return Collections.emptyList();
}
// 将解析出来的每个 Map 转换为一个 PropertySource 对象,并返回一个 PropertySource 列表
List<PropertySource<?>> propertySources = new ArrayList<>(loaded.size());
for (int i = 0; i < loaded.size(); i++) {
String documentNumber = (loaded.size() != 1) ? " (document #" + i + ")" : "";
propertySources.add(new OriginTrackedMapPropertySource(name + documentNumber, loaded.get(i)));
}
return propertySources;
}
}
public class PropertiesPropertySourceLoader implements PropertySourceLoader {
private static final String XML_FILE_EXTENSION = ".xml";
// 返回支持的文件扩展名
@Override
public String[] getFileExtensions() {
return new String[] { "properties", "xml" };
}
// 加载并解析 Properties 配置文件或 XML 配置文件
@Override
public List<PropertySource<?>> load(String name, Resource resource) throws IOException {
// 解析 Properties 文件或者 XML 文件,返回一个 Map
Map<String, ?> properties = loadProperties(resource);
// 如果解析出来的结果为空,则返回一个空列表
if (properties.isEmpty()) {
return Collections.emptyList();
}
// 将解析出来的 Map 转换为一个 PropertySource 对象,
// 并返回一个包含该 PropertySource 的列表
return Collections.singletonList(new OriginTrackedMapPropertySource(name, properties));
}
// 根据文件名后缀判断是解析 Properties 还是 XML 文件,并使用相应的解析器解析文件内容
@SuppressWarnings({ "unchecked", "rawtypes" })
private Map<String, ?> loadProperties(Resource resource) throws IOException {
String filename = resource.getFilename();
if (filename != null && filename.endsWith(XML_FILE_EXTENSION)) {
return (Map) PropertiesLoaderUtils.loadProperties(resource);
}
return new OriginTrackedPropertiesLoader(resource).load();
}
}
在 ConfigFileApplicationListener 中,通过上面各实现类的 getFileExtensions() 方法,动态选择适合的加载配置文件的实现类。
public class ConfigFileApplicationListener implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {
......
private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,
DocumentConsumer consumer) {
if (!StringUtils.hasText(name)) {
// 如果没有指定文件名,则循环遍历所有的 PropertySourceLoader,
// 找到能够加载该文件扩展名的加载器,并使用该加载器进行加载和解析
for (PropertySourceLoader loader : this.propertySourceLoaders) {
if (canLoadFileExtension(loader, location)) {
load(loader, location, profile, filterFactory.getDocumentFilter(profile), consumer);
return;
}
}
}
// 如果指定了文件名,则循环遍历所有的 PropertySourceLoader,
// 尝试使用每种文件扩展名去加载和解析该配置文件,
// 并将解析结果传递给 consumer 进行处理
Set<String> processed = new HashSet<>();
for (PropertySourceLoader loader : this.propertySourceLoaders) {
for (String fileExtension : loader.getFileExtensions()) {
if (processed.add(fileExtension)) {
// 在文件名中拼接上当前文件扩展名,
// 然后使用当前加载器进行加载和解析,
// 并将解析结果传递给 consumer 进行处理
loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory,
consumer);
}
}
}
}
......
}
以上就是 Spring Boot 源码中,启动时加载配置文件的过程,使用策略模式的大致实现过程。
五、总结
策略模式是一种常见的设计模式,在实际的软件开发中也经常被使用到。它通过将算法的实现逻辑封装到独立的策略类中,使得客户端可以动态地选择算法,从而提高了代码的灵活性、可扩展性和可维护性。在使用策略模式时,需要注意确保策略类的接口或抽象类设计合理、避免过度复杂化以及注意线程安全等问题。
对于本文中的内容,不知道你有没有什么看法,欢迎在评论区里留言。如果你对我的文章内容感兴趣,欢迎点击关注,谢谢支持![谢谢][谢谢][谢谢]
猜你喜欢
- 2024-09-30 Java策略模式在动态数据验证中的应用
- 2024-09-30 《设计模式》之策略模式(策略设计模式详解)
- 2024-09-30 Spring 中策略模式的 2 个经典应用
- 2024-09-30 Java策略模式实现动态验证不同来源的数据
- 2024-09-30 策略模式介绍以及具体使用场景(策略模式的优点及适用环境)
- 2024-09-30 什么是策略模式(策略模式的意图是什么)
- 2024-09-30 设计模式系列—策略模式(策略模式实现)
- 2024-09-30 快速带你彻底弄懂23种设计模式-策略模式
- 2024-09-30 「java设计模式」(7)——策略模式(案例解析)
- 2024-09-30 使用 Java 的策略设计模式(在java中找一个应用策略模式的实例)
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)