首页 > 其他分享 >策略模式(什么是策略模式、策略模式的应用场景、策略模式的优点和缺点、策略模式在项目中的应用)

策略模式(什么是策略模式、策略模式的应用场景、策略模式的优点和缺点、策略模式在项目中的应用)

时间:2024-09-21 12:55:10浏览次数:9  
标签:策略 pay 模式 PayEnum 应用 支付 import public

文章目录

1. 什么是策略模式

策略模式(Strategy Pattern)是一种行为型设计模式,它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户端

策略模式允许在运行时选择不同的算法行为,而不会影响到客户端代码


策略模式的主要角色:

  1. 策略接口(Strategy):定义所有支持的算法的公共操作,通常是一个抽象类或接口(抽象类和接口的区别可以参考我的另一篇博文:Java中接口和抽象类的区别(语法层面的区别、设计理念层面的区别
  2. 具体策略(Concrete Strategy):实现策略接口的类,实现了具体的算法
  3. 上下文(Context):使用策略接口的对象,负责维护一个具体策略对象的引用,并通过这个引用来调用具体策略实现的方法

策略模式符合设计模式六大基本原则中的开闭原则和单一职责原则(至于什么是设计模式的六大原则,可以参考我的另一篇博文:设计模式的六大基本原则(开闭原则、单一职责原则、里氏替换原则、接口隔离原则、依赖倒置原则、迪米特法则)

2. 策略模式的应用场景

以下是一些典型的使用策略模式的场景:

  1. 排序算法:在需要对数据进行排序时,可以使用策略模式来定义不同的排序算法(如快速排序、冒泡排序、归并排序等),然后在运行时根据需要选择合适的排序策略
  2. 支付方式:在电子商务系统中,不同的支付方式(如信用卡、PayPal、比特币等)可以被视为不同的策略,用户可以根据自己的偏好选择支付策略
  3. 折扣计算:在销售系统中,不同的折扣策略(如固定折扣、百分比折扣、阶梯折扣等)可以封装为不同的策略类,根据不同的销售活动选择合适的折扣策略
  4. 数据压缩:在文件处理或数据传输中,可以使用不同的数据压缩算法(如ZIP、GZIP、LZMA等),每种算法都可以是一个策略
  5. 日志记录:在日志管理中,可以将不同的日志记录方式(如文件记录、数据库记录、远程记录等)实现为策略
  6. 错误处理:在异常处理中,不同的错误处理策略(如重试、忽略、记录日志等)可以作为策略实现

策略模式的应用场景通常具有以下特点:

  • 多个类只区别在表现行为上,可以使用一个公共接口来定义这些行为
  • 算法的使用环境(客户端)不应该包含算法逻辑,这些算法逻辑应该被封装起来
  • 算法之间可以互换,客户端应该能够在运行时选择不同的算法
  • 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现

3. 策略模式的优点和缺点

3.1 优点

  • 算法可以自由切换:策略模式使得算法可以在不影响到客户端的情况下进行切换
  • 避免使用多重条件判断:在策略模式中,我们可以避免使用多重if-else或switch-case语句,使得代码更加简洁
  • 扩展性良好:策略模式具有良好的扩展性,当需要添加新的算法时,只需要实现策略接口即可
  • 维护性好:策略模式将算法封装在独立的类中,使得它们更容易维护和理解

3.2 缺点

  • 客户端需要知道所有的策略类:客户端需要知道有哪些策略类可用,才能选择合适的策略
  • 增加了对象的数目:策略模式会创建多个策略类和对象,在一定程度上增加了系统的复杂性

4. 策略模式在项目中的应用(以支付方式为例)

本次演示的环境:JDK 17.0.7 + SpringBoot 3.0.2

4.1 没有应用策略模式前的代码

以下代码的主要作用是根据支付类型调用对应的 Bean 的支付方法来完成支付功能

从业务代码的角度来说,这段代码是没有什么明显的问题的,但是如果以后支付的类型变得越来越多,switch 语句的分支也会越来越多,通过构造器传进来的 Bean 也会越来越多,不利于我们后期的维护

在这里插入图片描述

import cn.edu.scau.service.AliPayService;
import cn.edu.scau.service.UnionPayService;
import cn.edu.scau.service.WeChatPayService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class PayController {

    private final WeChatPayService wechatPayService;

    private final AliPayService aliPayService;

    private final UnionPayService unionPayService;

    public PayController(WeChatPayService wechatPayService, AliPayService aliPayService, UnionPayService unionPayService) {
        this.wechatPayService = wechatPayService;
        this.aliPayService = aliPayService;
        this.unionPayService = unionPayService;
    }

    @GetMapping("/pay")
    public void pay(Integer payType, Long orderId) {
        switch (payType) {
            case 1:
                wechatPayService.pay(orderId); // 微信支付
                break;
            case 2:
                aliPayService.pay(orderId); // 支付宝支付
                break;
            case 3:
                unionPayService.pay(orderId); // 银联支付
                break;
            default:
                throw new RuntimeException("不支持的支付方式");
        }
    }

}

4.2 应用策略模式改造代码

4.2.1 定义一个抽象的支付方式接口

PayStrategy.java

public interface PayStrategy {

    void pay(Long orderId);

}

4.2.2 让具体的支付方式实现抽象的支付方式接口

WeChatPayService.java

import org.springframework.stereotype.Service;

@Service
public class WeChatPayService implements PaymentStrategy {

    @Override
    public void pay(Long orderId) {
        System.out.println("微信支付");
    }

}

AliPayService.java

import org.springframework.stereotype.Service;

@Service
public class AliPayService implements PaymentStrategy {

    @Override
    public void pay(Long orderId) {
        System.out.println("支付宝支付");
    }

}

UnionPayService.java

import org.springframework.stereotype.Service;

@Service
public class UnionPayService implements PaymentStrategy {

    @Override
    public void pay(Long orderId) {
        System.out.println("银联支付");
    }

}

4.2.3 根据支付类型获取实现了对应支付方式的 Bean

我们需要一个策略的上下文(也叫策略工厂)来生产出具体的策略实现

通过策略上下文的构造方法进行自动装配,在容器初始化的时候就将三种具体的支付方式放入一个 Map 中(Spring 3.0 出现的新特性,可以通过 List 来接收多个 Bean)

为什么要放到一个 Map 里面呢,因为我们希望能够根据支付类型迅速地获取到实现了该种支付方式的 Bean

在这里插入图片描述

但现在有一个问题,在初始化 Map 的时候,key 应该是什么类型呢,1、2、3、4 吗,好像也不太合适

我们可以维护一个支付类型的枚举类型,把支付方式和支付类型对应的数字维护起来

在这里插入图片描述

public enum PayEnum {
    
    WECHAT_PAY(1),

    ALI_PAY(2),

    UNION_PAY(3);

    private final Integer value;


    PayEnum(Integer value) {
        this.value = value;
    }

    public Integer getValue() {
        return value;
    }

    public static PayEnum fromValue(Integer value) {
        for (PayEnum payEnum : PayEnum.values()) {
            if (payEnum.getValue().equals(value)) {
                return payEnum;
            }
        }

        throw new IllegalArgumentException("Invalid value: " + value);
    }

}

4.2.4 在抽象接口中添加获取获取具体支付对象的枚举类型

在这里插入图片描述

import cn.edu.scau.context.PayEnum;

public interface PaymentStrategy {

    void pay(Long orderId);

    PayEnum getPayEnum();
    
}

4.2.5 让具体的支付方法返回对应的枚举类型

WeChatPaymentService.java

import cn.edu.scau.context.PayEnum;
import org.springframework.stereotype.Service;

@Service
public class WeChatPaymentService implements PaymentStrategy {

    @Override
    public void pay(Long orderId) {
        System.out.println("微信支付");
    }

    @Override
    public PayEnum getPayEnum() {
        return PayEnum.WECHAT_PAY;
    }

}

AliPaymentService.java

import cn.edu.scau.context.PayEnum;
import org.springframework.stereotype.Service;

@Service
public class AliPaymentService implements PaymentStrategy {

    @Override
    public void pay(Long orderId) {
        System.out.println("支付宝支付");
    }

    @Override
    public PayEnum getPayEnum() {
        return PayEnum.ALI_PAY;
    }

}

UnionPaymentService.java

import cn.edu.scau.context.PayEnum;
import org.springframework.stereotype.Service;

@Service
public class UnionPaymentService implements PaymentStrategy {

    @Override
    public void pay(Long orderId) {
        System.out.println("银联支付");
    }

    @Override
    public PayEnum getPayEnum() {
        return PayEnum.UNION_PAY;
    }

}

4.2.6 添加枚举类型后的策略上下文

在这里插入图片描述

import cn.edu.scau.service.PaymentStrategy;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

@Component
public class PaymentStrategyContext {

    public final ConcurrentHashMap<PayEnum, PaymentStrategy> handleStrategyMap = new ConcurrentHashMap<>();

    public PaymentStrategyContext(List<PaymentStrategy> paymentStrategyList) {
        paymentStrategyList.forEach(paymentStrategy -> handleStrategyMap.put(paymentStrategy.getPayEnum(), paymentStrategy));
    }

}

我们还需要在策略上下文中添加一个根据支付类型获取对应支付对象的方法

在这里插入图片描述

import cn.edu.scau.service.PaymentStrategy;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

@Component
public class PaymentStrategyContext {

    public final ConcurrentHashMap<PayEnum, PaymentStrategy> handleStrategyMap = new ConcurrentHashMap<>();

    public PaymentStrategyContext(List<PaymentStrategy> paymentStrategyList) {
        paymentStrategyList.forEach(paymentStrategy -> handleStrategyMap.put(paymentStrategy.getPayEnum(), paymentStrategy));
    }

    public PaymentStrategy getPaymentStrategy(PayEnum payEnum) {
        return handleStrategyMap.get(payEnum);
    }

}

4.3 改造后的代码

应用策略模式后,我们就不需要在 controller 层逐个注入支付方式对应的 Bean 了,只需要注入策略上下文,然后根据策略上下文获取到对应支付方式对应的 Bean

import cn.edu.scau.context.PayEnum;
import cn.edu.scau.context.PaymentStrategyContext;
import cn.edu.scau.service.PaymentStrategy;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class PayController {

    private final PaymentStrategyContext paymentStrategyContext;

    public PayController(PaymentStrategyContext paymentStrategyContext) {
        this.paymentStrategyContext = paymentStrategyContext;
    }


    @GetMapping("/pay")
    public void pay(Integer payType, Long orderId) {
        PaymentStrategy paymentStrategy = paymentStrategyContext.getPaymentStrategy(PayEnum.fromValue(payType));
        paymentStrategy.pay(orderId);
    }

}

4.4 改造前和改造后代码的对比

改造前的代码

在这里插入图片描述

改造后的代码

在这里插入图片描述

我们可以看到,改造后的代码,无论是从代码的可读性,还是从后期的维护性来说,都得到了提升

我们后续如果要添加一个新的支付方式的话,只需要再创建一个新支付方式对应的 Bean,接着添加一个新的枚举类型就可以了

标签:策略,pay,模式,PayEnum,应用,支付,import,public
From: https://blog.csdn.net/m0_62128476/article/details/142322139

相关文章

  • 优化数据库结构:自定义元数据、索引与约束的应用
       当在导入预设表结构时,确实可以自定义一些额外的元数据来优化数据库结构。这些元数据不仅限于表的注释,还包括索引、约束等,这些都是为了提高查询性能、保证数据完整性和便于数据库管理而设计的。表注释表注释是用来描述表的作用、存储的数据类型等信息的文本信息。这......
  • Android插件化(三)基础之Android应用程序资源的编译和打包过程分析
    Android插件化(三)基础之Android应用程序资源的编译和打包过程分析Android资源加载常规思路getResourcesForApplication//首先,通过包名获取该包名的Resources对象Resourcesres=pm.getResourcesForApplication(packageName);//根据约定好的名字,去取资源id;intid=res.......
  • 数据飞轮与数据中台:媒体行业视角下的技术解析与应用
    在数据驱动的数字化转型浪潮中,媒体行业经历了从传统纸媒向数字化、个性化内容推荐的重大转变。这种改变归功于新兴的数据技术实践如数据飞轮和数据中台的引入。本文旨在探讨数据飞轮是否为数据中台的高阶形态,或者这两个概念之间存在本质的区别,并具体分析其在媒体行业中的应用。数......
  • 反DDD模式之“复用”
    本文书接上回《反DDD模式之关系型数据库》,关注公众号(老肖想当外语大佬)获取信息:最新文章更新;DDD框架源码(.NET、Java双平台);加群畅聊,建模分析、技术实现交流;视频和直播在B站。背景在我们软件开发过程中,“复用接口(webapi)”、“复用服务(service)”是非常常见的现象,很多老司机都会为自己设......
  • 使用 Rust 和 wasm-pack 开发 WebAssembly 应用
    一、什么是WebAssembly?WebAssembly是一种运行在现代Web浏览器中的新型二进制指令格式。它是一种低级别的字节码,可以被多种语言编译,并在浏览器中高效运行。1.1WebAssembly的背景与概念高性能计算:WebAssembly旨在提高Web应用的性能,接近原生速度,适合计算密集型任务......
  • 反DDD模式之“复用”
    本文书接上回《反DDD模式之关系型数据库》,关注公众号(老肖想当外语大佬)获取信息:最新文章更新;DDD框架源码(.NET、Java双平台);加群畅聊,建模分析、技术实现交流;视频和直播在B站。背景在我们软件开发过程中,“复用接口(webapi)”、“复用服务(service)”是非常常见的现象,很多......
  • 数据飞轮与数据中台的本质区别及其在制造业的应用
    在当下的数据驱动时代,数据中台与数据飞轮成为两个高频出现的关键词,它们代表了数据管理和分析的前沿发展趋势。在探讨这两者的关系及其在制造业中的实际应用之前,有必要先厘清数据中台和数据飞轮的概念及其基本功能。数据中台是一个集数据集成、处理、存储、分析及服务于一体的中间......
  • 【C++篇】C++类与对象深度解析(六):全面剖析拷贝省略、RVO、NRVO优化策略
    文章目录C++类与对象前言读者须知RVO与NRVO的启用条件如何确认优化是否启用?1.按值传递与拷贝省略1.1按值传递的概念1.2示例代码1.3按值传递的性能影响1.3.1完全不优化1.4不同编译器下的优化表现1.4.1VisualStudio2019普通优化1.4.2VisualStudio2022激进......
  • 掌握新趋势:数据飞轮在出行行业的应用
    随着数据科技的迅猛发展和企业竞争的日益激烈,从单纯的数据仓库、到数据中台,再到现今越来越受瞩目的数据飞轮,我们见证了数据管理和分析技术的一次次演变。尤其在出行行业,这种转变不仅推动了技术的进步,还深刻改变了市场结构和业务模式。出行行业的数据挑战与机遇出行行业涵盖了公共......
  • 公私域互通下的新商机探索:链动2+1模式、AI智能名片与S2B2C商城小程序的融合应用
    摘要:在数字化时代,公私域流量的有效融合已成为企业获取持续增长动力的关键。本文旨在探讨如何通过链动2+1模式、AI智能名片以及S2B2C商城小程序源码的综合运用,实现公私域流量的高效互通,进而为门店创造巨大商机。通过分析这些工具的特点、作用机制及其实施策略,本文为企业在复杂多......