Java 设计模式之策略模式 (Strategy Pattern) 详解

news2024/11/22 18:11:55

Java 设计模式之策略模式 (Strategy Pattern) 详解

策略模式(Strategy Pattern)是一种行为型设计模式,旨在定义一系列算法,将每个算法封装起来,并使它们可以互相替换,从而使得算法的变化不会影响使用算法的客户端。策略模式的主要结构包括策略接口、具体策略类和上下文类,通过将算法的选择与使用分离,实现了代码的可维护性和灵活性。

更多设计模式请参考:Java 中的 23 种设计模式详解

1. 策略模式的动机

在软件开发中,经常遇到需要在运行时动态选择一种算法的情况。例如,排序算法、支付方式、文件压缩等场景都可能需要在不同条件下选择不同的算法实现。如果在客户端代码中硬编码这些算法的选择逻辑,会导致代码难以维护和扩展。策略模式通过将算法的选择和实现分离,使得算法可以独立变化,客户端代码可以更简洁和灵活。

2. 策略模式的结构

策略模式包含以下几部分:

  • 策略接口(Strategy Interface):定义所有支持的算法的公共接口。
  • 具体策略类(Concrete Strategies):实现策略接口,定义具体的算法。
  • 上下文类(Context Class):使用一个具体策略对象来配置,并维护对策略对象的引用。
3. 策略模式的UML类图

在这里插入图片描述

4. 策略模式的实现

以下是一个使用策略模式的Java示例,该示例演示了如何选择不同的策略来执行操作:

4.1 策略接口
// 定义策略接口
public interface Strategy {
    void execute();
}
4.2 具体策略类
// 具体策略A
public class ConcreteStrategyA implements Strategy {
    @Override
    public void execute() {
        System.out.println("执行策略A");
    }
}

// 具体策略B
public class ConcreteStrategyB implements Strategy {
    @Override
    public void execute() {
        System.out.println("执行策略B");
    }
}
4.3 上下文类
// 上下文类
public class Context {
    private Strategy strategy;

    // 设置策略
    public void setStrategy(Strategy strategy) {
        this.strategy = strategy;
    }

    // 执行策略
    public void executeStrategy() {
        if (strategy == null) {
            throw new IllegalStateException("Strategy未设置");
        }
        strategy.execute();
    }
}
4.4 客户端代码
public class StrategyPatternDemo {
    public static void main(String[] args) {
        Context context = new Context();

        // 使用策略A
        context.setStrategy(new ConcreteStrategyA());
        context.executeStrategy(); // 输出: 执行策略A

        // 使用策略B
        context.setStrategy(new ConcreteStrategyB());
        context.executeStrategy(); // 输出: 执行策略B
    }
}

5. 策略模式的优缺点

优点
  1. 算法可以自由切换:可以在不影响客户端的情况下更改算法。
  2. 避免多重条件判断:使用策略模式可以避免过多的if-else或switch-case语句。
  3. 扩展性好:增加新的策略时只需添加新的策略类即可,不需要修改现有代码。
缺点
  1. 客户端必须知道所有的策略类:客户端需要了解每个策略类的具体实现,这增加了复杂度。
  2. 增加对象数目:如果策略较多,会增加类的数量,导致系统变得复杂。

6. 策略模式的应用场景

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

  • 需要在不同情况下使用不同的算法。
  • 有许多相关类仅仅在行为上有所不同。
  • 需要避免使用复杂的条件语句来选择不同的行为。

7. 策略模式的变体

策略模式可以与其他设计模式结合使用,以增强其功能。例如:

  • 组合模式(Composite Pattern):可以将策略模式与组合模式结合,使得策略的选择更加灵活。
  • 工厂模式(Factory Pattern):可以使用工厂模式来创建策略对象,从而实现策略的动态选择。

8. 策略模式与其他设计模式的比较

  • 策略模式 vs. 状态模式:两者结构类似,但策略模式的不同策略是彼此独立的,而状态模式的不同状态之间存在一定的关系。
  • 策略模式 vs. 命令模式:命令模式用于封装请求,将请求与执行解耦,而策略模式用于封装算法,将算法与使用算法的代码解耦。

9. 策略模式的实现细节与最佳实践

9.1 延迟初始化策略

在某些情况下,策略的初始化可能比较耗时,可以使用延迟初始化(Lazy Initialization)来提高性能:

public class Context {
    private Strategy strategy;

    public void setStrategy(Strategy strategy) {
        this.strategy = strategy;
    }

    public void executeStrategy() {
        if (strategy == null) {
            // 延迟初始化
            strategy = new ConcreteStrategyA();
        }
        strategy.execute();
    }
}
9.2 使用反射动态加载策略

为了避免频繁修改代码,可以通过反射动态加载策略:

public class Context {
    private Strategy strategy;

    public void setStrategy(String strategyClassName) throws Exception {
        this.strategy = (Strategy) Class.forName(strategyClassName).getDeclaredConstructor().newInstance();
    }

    public void executeStrategy() {
        strategy.execute();
    }
}
9.3 使用配置文件管理策略

将策略的配置放在配置文件中,便于管理和维护:

# strategy.properties
strategy=ConcreteStrategyA
import java.io.InputStream;
import java.util.Properties;

public class StrategyLoader {
    public static Strategy loadStrategy() throws Exception {
        Properties properties = new Properties();
        try (InputStream input = StrategyLoader.class.getResourceAsStream("/strategy.properties")) {
            properties.load(input);
        }
        String strategyClassName = properties.getProperty("strategy");
        return (Strategy) Class.forName(strategyClassName).getDeclaredConstructor().newInstance();
    }
}
9.4 策略模式与依赖注入

结合依赖注入框架(如Spring),可以更加灵活地管理策略的实例:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Context {
    private final Strategy strategy;

    @Autowired
    public Context(Strategy strategy) {
        this.strategy = strategy;
    }

    public void executeStrategy() {
        strategy.execute();
    }
}

10. 策略模式的实际应用案例

在这里插入图片描述

10.1 支付系统中的策略模式

在一个支付系统中,可能有多种支付方式,如信用卡支付、支付宝支付、微信支付等。通过策略模式,可以根据用户选择的支付方式动态切换支付策略。

支付策略接口
public interface PaymentStrategy {
    void pay(double amount);
}
具体支付策略类
// 信用卡支付策略
public class CreditCardPaymentStrategy implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        System.out.println("使用信用卡支付:" + amount + "元");
    }
}

// 支付宝支付策略
public class AliPayPaymentStrategy implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        System.out.println("使用支付宝支付:" + amount + "元");
    }
}

// 微信支付策略
public class WeChatPaymentStrategy implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        System.out.println("使用微信支付:" + amount + "元");
    }
}
支付上下文类
public class PaymentContext {
    private PaymentStrategy paymentStrategy;

    // 设置支付策略
    public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
        this.paymentStrategy = paymentStrategy;
    }

    // 执行支付
    public void pay(double amount) {
        if (paymentStrategy == null) {
            throw new IllegalStateException("PaymentStrategy未设置");
        }
        paymentStrategy.pay(amount);
    }
}
支付策略工厂类

为了更加优雅地创建支付策略,可以使用工厂模式:

public class PaymentStrategyFactory {
    public static PaymentStrategy getPaymentStrategy(String type) {
        switch (type) {
            case "CreditCard":
                return new CreditCardPaymentStrategy();
            case "AliPay":
                return new AliPayPaymentStrategy();
            case "WeChat":
                return new WeChatPaymentStrategy();
            default:
                throw new IllegalArgumentException("未知的支付类型: " + type);
        }
    }
}
客户端代码


public class PaymentDemo {
    public static void main(String[] args) {
        PaymentContext context = new PaymentContext();

        // 从外部获取支付类型,例如通过用户输入或配置文件
        String paymentType = "CreditCard"; // 这里可以根据实际情况更改

        // 使用工厂创建支付策略
        PaymentStrategy paymentStrategy = PaymentStrategyFactory.getPaymentStrategy(paymentType);
        
        // 设置支付策略
        context.setPaymentStrategy(paymentStrategy);

        // 执行支付
        context.pay(100.0); // 输出: 使用信用卡支付:100.0元

        // 更改支付策略
        paymentType = "AliPay";
        paymentStrategy = PaymentStrategyFactory.getPaymentStrategy(paymentType);
        context.setPaymentStrategy(paymentStrategy);
        context.pay(200.0); // 输出: 使用支付宝支付:200.0元

        // 更改支付策略
        paymentType = "WeChat";
        paymentStrategy = PaymentStrategyFactory.getPaymentStrategy(paymentType);
        context.setPaymentStrategy(paymentStrategy);
        context.pay(300.0); // 输出: 使用微信支付:300.0元
    }
}

优化的重点

  1. 工厂模式:使用工厂模式来创建支付策略对象,使客户端代码更简洁,策略的创建和选择更灵活。
  2. 空策略检查:在上下文类中增加对策略是否为空的检查,避免未设置策略时的运行时错误。
  3. 策略类型动态获取:通过从外部(如用户输入或配置文件)获取支付类型,示例代码更加接近实际应用场景。

通过策略模式和工厂模式的结合,可以实现一个灵活、可扩展且易于维护的支付系统。在实际开发中,进一步结合依赖注入框架(如Spring)来管理策略对象,可以提升代码的可测试性和可扩展性。

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

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

相关文章

掩码、反掩码、通配符的区别和计算方式

注:几篇文章合集捋清掩码、反掩码、通配符的区别。 ACL 通配符,子网掩码,反掩码区别和计算方式 192.168.1.1 255.255.255.0 掩码: 1111111111111111000000000 左边永远是 1,右边永远是 0 1 和 0 ,永远不…

UE Spinbox 控件支持Double类型及Float精度问题

UE Spinbox 控件支持Double类型及Float精度问题 问题 众所周知,使用float来表示GIS坐标时,往往由于坐标的有效数字超过7位时,无法表达坐标的小数部分,即精度不够。 方案 1、取区域中心点的局部坐标的形式,使得坐标…

C#列表按照日期进行从大到小排序

C#列表按照日期进行从大到小排序

多路径 bbr mpbbr 公平性推演

mptcp 推出很久了,先看 rfc6356 三原则: 对自己,mptcp 的吞吐不能比用 sp(single path)tcp 时更差;对它者,mptcp 子流对资源的占用不能侵害其它 sptcp 流量;负载分担,要将孬 subflow 流量分担到…

Unity Rigidbody 踩坑记录

1:两个带有刚体的物体碰撞会一直不停的弹 把被动受力的刚提的 Freeze Position 的勾选 去掉(碰到过一次,有一种受力无法释放又返回给目标的 所以一直弹跳的感觉) 2:子物体 和父物体 都有刚体的情况下 子物体 Freeze R…

WEB渗透Web突破篇-PHP文件包含下载读取

php任意文件读取/下载 readfile()、file_get_contents()、fopen()等读文件的函数不严谨,读取文件路径可控,输出内容。 下载配置文件 Redis、Weblogic、ftp、mysql、web配置文件、history文件、数据库配置文件 下载log文件 下载web文件 /1.php?f../../e…

C#实现数据采集系统-配置文件化

系统优化-配置 配置信息ip端口,还有点位信息,什么的都是直接在代码里直接写死,添加点位,修改配置,比较麻烦,每次修改都需要重新生成打包。 所以将这些配置都改成配置文件,这样只需要修改配置文件,程序无须修改,即可更新。 配置代码: 如果我们有100个采集,一个个去…

ReactiveStream

消息传递是响应式核心 ReactiveStream规范核心接口 ReactiveStream发布数据 发布者要保存订阅者 发布者要知道谁要拿他的数据,发布者要知道他把数据要给谁 reactive stream发布订阅的写法 subscribe订阅者 publish发布者 subscription 绑定关系 onXXX事件…

MySQL:基础增删查改

MySQL:基础增删查改 插入插入冲突 查询distinctwhereorder bylimit 删除deletetruncate 更新 插入 基本插入语法: insert [into] 表名 (列1, 列2 ...) values (值1, 值2 ...);into可以省略(列1, 列2 ...)与后面的(值1, 值2)一一对应如果插入时数据完全…

药明康德:头顶达摩克利斯之剑

头顶达摩克里斯之剑,CXO龙头——药明康德的日子好过吗? 在行业下行周期、美国法案阴霾压力之下,药明康德交出今年上半年的成绩单,营收净利双双下滑,净利润更是同比减少20.2%。 看起来情况糟糕,但显然让被法…

基于YOLOv8的船舶检测系统

基于YOLOv8的船舶检测系统 (价格85) 包含 【散货船,集装箱船,渔船,杂货船,矿砂船,客船】 6个类 通过PYQT构建UI界面,包含图片检测,视频检测,摄像头实时检测。 (该…

【竞技宝】DOTA2雪如意:ame水人逆转 XG力克NAVI

北京时间2024年8月2日,DOTA2雪如意杯在昨天将迎来淘汰赛首个比赛日,本日一共进行四场比赛,第二场比赛由NAVI对阵XG。本场比赛双方前两局战至1-1平,决胜局XG前期局势很劣,但中期拖住局势等到了ame水人装备成型,最终XG2-1击败NAVI。以下是本场比赛的详细战报。 第一局: 首局比赛…

领夹无线麦克风哪个牌子好,麦克风哪个好,2024年无线麦克风推荐

​无论是采访、Vlog还是直播,清晰的音频录制都极为重要。作为一名有经验的自媒体人,我深知哪些无线领夹麦克风能够在不破坏预算的情况下提供出色的录音效果。以下是我个人推荐的几个高性能、低价位的无线领夹麦克风,它们将使你的音频录制更上…

深度学习,机器学习,强化学习

深度学习、机器学习、python、人工智能项目代做和指导答疑~擅长(svm、罗辑回归、贝叶斯、决策树、xgboost等)机器学习算法、各种opencv图像处理、图像分类模型(vgg、resnet、mobilenet、efficientnet、densenet等)、人…

协同作战:Axure 团队协作功能全解析

前言 在当今快节奏的数字产品开发领域,团队协作不仅是成功的关键,更是创新的催化剂。 Axure,作为一款领先的原型设计工具,其内置的团队协作功能为设计师、产品经理和开发人员提供了一个无缝的沟通和协作平台。 让我们一起探索这…

每日一题——贪心算法

1005. K 次取反后最大化的数组和 - 力扣(LeetCode) 题解: 一开始有点理解错他的意思,以为是i是题目中会给出,所以一开始没有什么思路,然后当看了题解之后,就知道了原来i是自己订的&#xff0c…

【hloc】 项目流程

hloc 项目流程 1. 数据集准备2. 特征提取3. 匹配特征4. 三维重建5. 定位6. 结果评估7. 示例脚本 这个项目涉及到了视觉定位和三维重建的一系列步骤,从特征提取、匹配、三维重建到定位和结果评估。通过提供的脚本文件,用户可以方便地运行整个流程。 1. 数…

PLC-Recorder对于数据采集时间戳偏差的修正功能

目录 一、修正原理 二、使用步骤 1、初始状态,计算修正系数 2、启动和使用 3、修正系数的手动修改 三、修正前后的效果对比 1、修正前的时间偏差曲线 2、修正后的效果曲线 四、注意事项 1、控制器可能的时钟波动 2、平移参数评估 3、参数保存 五、关于…

红酒与夜晚:享受静谧的品酒时光

当夜幕低垂,星光点点,世界仿佛进入了一个宁静而神秘的领域。在这样的夜晚,与一瓶定制红酒洒派红酒(Bold & Generous)相伴,便是一场令人陶醉的品酒之旅,让人在静谧中感受生活的美好。 一、夜…

【深度学习】【语音TTS】OpenVoice: Versatile Instant Voice Cloning,论文

https://github.com/myshell-ai/OpenVoice https://arxiv.org/abs/2312.01479 文章目录 摘要1 引言2 方法2.1 直观思路2.2 模型结构2.3 训练细节3 结果4 结论摘要 我们介绍了OpenVoice,一种多功能的即时语音克隆方法,只需参考说话者的短音频片段即可复制其声音,并生成多语…