一文教会你巧用设计模式重构项目

news2024/11/18 19:43:23

文章目录

    • 一、设计模式总览
    • 二、模板方法模式案例
    • 三、策略模式案例
    • 四、支付改造
      • 4.1 思路分析
      • 4.2 实现图解:
      • 4.3 代码实现:
      • 4.4 效果演示
      • 4.5 如何扩展

本文参考自12.29日尚硅谷雷神的 巧妙使用设计模式重构项目

一、设计模式总览

  • 总体分类

image-20230113210444492

  • 不同时期选择不同的设计模式~

image-20230114113220058

  • 设计模式本质上玩的就是:封装继承多态
  • 设计模式遵循的六大原则

image-20230114113416409

二、模板方法模式案例

**描述:**父类(接口、抽象类)提供了一种定义算法的骨架允许子类为一个或多个步骤提供实现,在不改变算法结构的情况下,重新定义算法的某些步骤。

案例:订单处理

1、定义订单处理模板

/**
 * @author lfy
 * @Description 定义订单处理模板
 * @create 2022-12-29 20:21
 */
public abstract class OrderProcessTemplate {

    /**
     * 处理订单: 定义好算法骨架
     */
    public final void processOrder(){
        //1、选择商品
        doSelect();
        //2、进行支付
        doPayment();
        //3、开具发票
        doReceipt();
        //4、派送商品
        doDelivery();

    }


    public abstract void doSelect();
    public abstract void doPayment();
    public abstract void doReceipt();
    public abstract void doDelivery();


}

2、定义实现类:NetOrderStoreOrder

/**
 * @author lfy
 * @Description 网络订单:算法细节实现
 * @create 2022-12-29 20:24
 */
public class NetOrder extends OrderProcessTemplate {
    @Override
    public void doSelect() {
        System.out.println("把 xiaomi11 加入购物车");
    }

    @Override
    public void doPayment() {
        System.out.println("在线微信支付 1999");
    }

    @Override
    public void doReceipt() {
        System.out.println("发票已经发送给用户邮箱: aaaa@qq.com");
    }

    @Override
    public void doDelivery() {
        System.out.println("顺丰次日达:投送商品");
    }
}
/**
 * @author lfy
 * @Description 门店订单:子类实现具体算法
 * @create 2022-12-29 20:26
 */
public class StoreOrder extends OrderProcessTemplate {
    @Override
    public void doSelect() {
        System.out.println("用户选择了:3号货架-xiaomi11 商品");
    }

    @Override
    public void doPayment() {
        System.out.println("刷卡机:刷卡支付 1999");
    }

    @Override
    public void doReceipt() {
        System.out.println("打印发票,和物品一起包装");
    }

    @Override
    public void doDelivery() {
        System.out.println("把商品交给用户,用漂亮的袋子");
    }
}

3、进行测试

/**
 * 设计模式:  多定义接口、抽象类
 * 1)、依赖倒置; 依赖抽象
 * 2)、多态;    随便替换实现
 * @author lfy
 * @Description 模板方法模式测试 ; 核心: 父类定义算法骨架,子类实现算法细节
 * @create 2022-12-28 20:41
 */
public class TemplateMethodPatternTest {

    public static void main(String[] args) {
        //行为型模式玩的就是一个多态
        //1、外界调用模板类【遵循依赖反转原则】【依赖抽象而不是细节】
        OrderProcessTemplate processTemplate = new NetOrder();
        System.out.println("网络订单:");
        //处理订单
        processTemplate.processOrder(); //定义了算法的模板


        processTemplate = new StoreOrder();
        System.out.println("门店订单:");
        // 根据需要,选择不同的实现类,从而达到我们需要的效果:网络订单 或 门店订单
        processTemplate.processOrder();
    }
}

运行结果:

image-20230113220611870

三、策略模式案例

**描述:**定义算法家族,分别封装起来,让它们之间可以相互替换,此模式让算法的变化独立于使用算法的客户。

案例:排序算法

1、定义算法接口

/**
 * @author lfy
 * @Description 算法接口:排序策略
 * @create 2022-12-29 20:36
 */
public interface SortStrategy {

    /**
     * 排序
     */
    void sort(Integer[] arr);
}

2、定义策略类,实现算法接口

/**
 * @author lfy
 * @Description 策略1:冒泡排序策略
 * @create 2022-12-29 20:38
 */
public class BubbleSortStrategy implements SortStrategy {
    @Override
    public void sort(Integer[] arr) {
        System.out.println("开始冒泡排序....");
        for (int i=0;i< arr.length-1;i++){
            for (int j = 0; j < arr.length- 1 - i  ; j++) {
                if(arr[j] > arr[j+1]){
                    Integer temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }
            }
        }
        System.out.println("排序结果:"+ Arrays.asList(arr));

    }
}
/**
 * @author lfy
 * @Description
 * @create 2022-12-29 20:44
 */
public class QuickSortStrategy implements SortStrategy {
    @Override
    public void sort(Integer[] arr) {
        System.out.println("快速排序开始");
        Arrays.sort(arr);
        System.out.println("排序结果:"+Arrays.asList(arr));

    }
}

3、定义上下文对象(入口类):持有策略对象

/**
 * @author lfy
 * @Description 上下文:入口类
 * @create 2022-12-29 20:45
 */
public class SortService {
    /**
     * 拿到一个排序算法
     */
    private SortStrategy strategy;

    /**
     * 为了强制要求用户必须传入一个排序算法
     * @param strategy
     */
    public SortService(SortStrategy strategy){
        this.strategy  =strategy;
    }

    /**
     * 随时动态更新排序算法
     * @param strategy
     */
    public void setStrategy(SortStrategy strategy) {
        this.strategy = strategy;
    }

    /**
     * 才是别人调用的排序方法
     * @param arr
     */
    public void sort(Integer[] arr){
        strategy.sort(arr);
    }
}

4、使用策略对象执行算法

/**
 * @author lfy
 * @Description  模板模式定义大框架、策略默认定义小细节
 * @create 2022-12-28 21:17
 */
public class StrategyPatternTest {

    public static void main(String[] args) {
        Integer[] arr = new Integer[]{2,4,6,3,1,7,9,8};
		// 传入不同的策略,就使用对应的策略
        SortService sortService = new SortService(new BubbleSortStrategy());
        sortService.sort(arr);

        System.out.println("===============");

        //更新策略
        sortService.setStrategy(new QuickSortStrategy());
        sortService.sort(arr);

    }
}

测试结果:

image-20230113220248469

四、支付改造

4.1 思路分析

使用模板方法模式:定义支付的完整流程

使用策略模式:定义支付的不同实现

4.2 实现图解:

分析:PayStrategy使用的是策略模式~ PayService中的processNotify里面包含了 一系列抽象方法的调用,不同的支付方式会有不同的实现,这里使用了模板方法模式~

4.3 代码实现:

1、支付策略接口:PayStrategy

/**
 * @author lfy
 * @Description 支付策略
 * @create 2022-12-28 22:39
 */
public interface PayStrategy {

    /**
     * 支持哪种支付
     * @param type
     * @return
     */
    boolean supports(String type);

    /**
     * 为某个订单展示收银台页面
     * @return
     */
    String cashierPage(OrderInfo orderInfo);

    /**
     * 验证签名
     * @param request  原生请求
     * @param body     请求体数据【请求体只能读取一次,所以controller拿到以后都往下传递即可】
     * @return
     */
    boolean checkSign(HttpServletRequest request,String body);


    /**
     * 验签错误处理
     * @return
     */
    Object signError();


    /**
     * 验签通过返回
     * @return
     */
    Object signOk();

    /**
     * 验签成功后处理通知数据: 把通知的所有数据封装指定对象
     * @param request
     * @return
     */
    Map<String,Object> process(HttpServletRequest request,String body);
}

2、接口实现:AlipayStrategyWeixinPayStrategy

/**
 * @author lfy
 * @Description 支付宝
 * @create 2022-12-28 22:40
 */
@Slf4j
@Component
public class AlipayStrategy implements PayStrategy {

    @Autowired
    AlipayProperties alipayProperties;

    @Autowired
    AlipayClient alipayClient;

    @Override
    public String cashierPage(OrderInfo orderInfo) {
        //1、创建一个 AlipayClient

        //2、创建一个支付请求
        AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();

        //3、设置参数
        alipayRequest.setReturnUrl(alipayProperties.getReturn_url()); //同步回调:支付成功以后,浏览器要跳转到的页面地址
        alipayRequest.setNotifyUrl(alipayProperties.getNotify_url()); //通知回调:支付成功以后,支付消息会通知给这个地址


        //商户订单号(对外交易号)
        String outTradeNo = orderInfo.getId().toString();
        //付款金额
        BigDecimal totalAmount = orderInfo.getPrice();
        //订单名称
        String orderName = "尚品汇-订单-"+outTradeNo;
        //商品描述
        String tradeBody = orderInfo.getDesc();

        //详细:https://opendocs.alipay.com/open/028r8t?scene=22
        //业务参数
        Map<String,Object> bizContent = new HashMap<>();
        bizContent.put("out_trade_no",outTradeNo);
        bizContent.put("total_amount",totalAmount);
        bizContent.put("subject",orderName);
        bizContent.put("body",tradeBody);
        bizContent.put("product_code","FAST_INSTANT_TRADE_PAY");
        //自动关单
        String date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(orderInfo.getExpireTime());
        bizContent.put("time_expire",date);
        alipayRequest.setBizContent(JSON.toJSONString(bizContent));


        //生成支付页面
        String page = null;
        try {
            page = alipayClient.pageExecute(alipayRequest).getBody();
        } catch (AlipayApiException e) {
            e.printStackTrace();
        }

        return page;
    }

    @Override
    public boolean checkSign(HttpServletRequest request,String body) {
        Map<String, String> params = HttpUtils.getParameterMap(request);
        log.info("支付宝通知验证签名...");
        //验证签名
        try {
            //调用SDK验证签名
            boolean signVerified = AlipaySignature.rsaCheckV1(params,
                    alipayProperties.getAlipay_public_key(), alipayProperties.getCharset(),
                    alipayProperties.getSign_type());
            return signVerified;
        } catch (AlipayApiException e) {
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public Object signError() {
        return "error";
    }

    @Override
    public Map<String,Object> process(HttpServletRequest request,String body) {
        Map<String, String> map = HttpUtils.getParameterMap(request);
        String json = JSON.toJSONString(map);
        Map<String, Object> data = JSON.parseObject(json, new TypeReference<Map<String, Object>>() {
        });
        return data;
    }

    @Override
    public Object signOk() {
        //支付宝要求成功返回 success 字符串
        return "success";
    }


    @Override  //对新增开放,对修改关闭
    public boolean supports(String type) {
        return "alipay".equalsIgnoreCase(type);
    }
}
/**
 * @author lfy
 * @Description 微信支付
 * @create 2022-12-28 22:40
 */
@Slf4j
@Component
public class WeixinPayStrategy implements PayStrategy {
    // 具体实现省略,可以到代码仓库查看
}

3、支付Service代码编写

/**
 * @author lfy
 * @Description
 * @create 2022-12-28 22:36
 */
public interface PayService {

    /**
     * 生成支付收银台页
     * @param type
     * @param orderId
     * @return
     */
    String payPage(String type, Long orderId);


    /**
     * 处理支付通知
     * @param request
     * @return
     */
    Object processNotify(HttpServletRequest request,String body);
}

4、支付Service实现

/**
 * @author lfy
 * @Description 支付上下文引用支付策略; 这个上下文也是模板类;定义好算法步骤
 * @create 2022-12-28 22:46
 */
@Service
@Slf4j //模板类
public class PayServiceImpl implements PayService {

    @Autowired
    List<PayStrategy> payStrategies; //注入支付策略

    /**
     * 生成收银台页面
     * @param type
     * @param orderId
     * @return
     */
    @Override
    public String payPage(String type, Long orderId) {
        //1、查询数据库订单
        OrderInfo orderInfo = getOrderInfo(orderId);

        //2、生成支付页
        for (PayStrategy strategy : payStrategies) {
            if(strategy.supports(type)){
                //获取收银台页面
                return strategy.cashierPage(orderInfo);
            }
        }
        //3、如果以上都不支持,打印错误
        return "不支持这种支付方式";
    }


    /**
     * 定义通知处理模板;
     * 微信通知
     * 支付宝通知
     * 1)、验证签名
     * 2)、验证通过改订单为已支付
     * 3)、验证通过给支付宝(success)微信(200状态码json)返回数据
     * 4)、xxx
     * @param request
     * @param body
     * @return
     */
    @Override
    public Object processNotify(HttpServletRequest request,String body) {
        Object result = "不支持此方式";

        //1、判断是那种通知
        String type = getNotifyType(request);
        Map<String, Object> data = null;

        //2、验证签名
        for (PayStrategy strategy : payStrategies) {
            if(strategy.supports(type)){
                //签名校验
                boolean checkSign = strategy.checkSign(request,body);
                if(!checkSign){
                    log.error("签名验证失败,疑似攻击请求");
                    //验签失败返回
                   return strategy.signError();
                }else {
                    log.info("签名验证成功,提取通知数据");
                    //验签成功处理数据
                    data = strategy.process(request,body);
                    //验签成功返回
                    result = strategy.signOk();
                }
            }
        }

        //3、通用的后续处理算法;处理订单数据
        processOrder(data);

        return result;
    }

    /**
     * 处理订单数据
     * @param data
     */
    private void processOrder(Map<String, Object> data) {
        //TODO 把支付成功信息等保存数据库,并修改订单状态,通知库存系统等...
        log.info("订单支付成功,状态修改完成,已通知库存系统,详细数据:{}",data);
    }

    /**
     * 判断通知类型
     * @param request
     * @return
     */
    private String getNotifyType(HttpServletRequest request) {
        String header = request.getHeader("wechatpay-serial");
        if(StringUtils.hasText(header)){
            return "weixin";
        }

        String app_id = request.getParameter("app_id");
        if(StringUtils.hasText(app_id)){
            return "alipay";
        }


        return "unknown";
    }

    public OrderInfo getOrderInfo(Long orderId){
        log.info("查询数据库订单:{}",orderId);
        OrderInfo orderInfo = new OrderInfo();
        orderInfo.setId(orderId);
        orderInfo.setTitle("尚品汇-商城-订单");
        orderInfo.setComment("快点发货");
        orderInfo.setDesc("买了一堆商品");
        orderInfo.setPrice(new BigDecimal("9098.00"));
        orderInfo.setExpireTime(new Date(System.currentTimeMillis()+30*60*1000));

        return orderInfo;
    }
}

分析:

image-20230113223331649

image-20230114125758333

4.4 效果演示

1、用户进入支付页面后:

image-20230114112440663

2、当选择微信支付时,进入微信二维码页面

image-20230114112525508

3、当选择支付宝支付,进入支付宝页面

image-20230114112626541

4、支付成功之后会跳转到对应的成功页面(微信成功页面、支付宝成功页面…)

4.5 如何扩展

最后我们需要思考一个问题,经过我们使用策略+模板改造的支付 和传统的写法有啥好处呢?

1、易于扩展,如果我们想要增加新的支付方式:银联支付、白条支付…我们只需要定义对应的支付策略类即可~ 当不需要时,直接删除对应的实现类。然后在调用的时候,会自适应使用对应的策略~ 符合对修改关闭,对扩展开放的特点!

image-20230114124615619

2、Service中的模板方法也让我们的代码冗余度更低,逻辑更加清晰~

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

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

相关文章

Python批量检索论文被引用数量源码,利用百度学术网页版来批量检索论文的被引用数量源码

论文被引用数搜索 利用百度学术网页版来检索一个文件夹中的所有论文的被引用数量。 完整代码下载地址&#xff1a;Python批量检索论文被引用数量源码 依赖有beautifulsoup库、regex正则表达式库。 使用方法 主程序为fileWalk.py。 修改程序中workPath值为文件夹绝对路径&am…

基于碰撞传感器的自动导航车系统设计

1、内容简介略635-可以交流、咨询、答疑2、内容说明略随着世界各国对科学技术的重视&#xff0c;各类高科技技术突飞猛进&#xff0c;人类逐步进入人工智能时代。而在这些高科技技术的背后&#xff0c;自动导航小车作为无人驾驶小车的一种类型备受关注。它的主要优点是不需要人…

冰冰学习笔记:智能指针

欢迎各位大佬光临本文章&#xff01;&#xff01;&#xff01; 还请各位大佬提出宝贵的意见&#xff0c;如发现文章错误请联系冰冰&#xff0c;冰冰一定会虚心接受&#xff0c;及时改正。 本系列文章为冰冰学习编程的学习笔记&#xff0c;如果对您也有帮助&#xff0c;还请各位…

vite 在proxy代理中更改headers

vite 在proxy代理中更改headers 平时我们在对接接口时&#xff0c;我们都是配置代理解决跨域问题 proxy: {^/api: {target: envConfig.VITE_APP_BASE_URL,changeOrigin: true,rewrite: (path) > path.replace(/^/api/, ),}} 某天你明明配置好了代理&#xff0c;浏览器还是会…

嵌入式Linux-守护进程

1.守护进程 1.1 何为守护进程 守护进程&#xff08;Daemon&#xff09;也称为精灵进程&#xff0c;是运行在后台的一种特殊进程&#xff0c;它独立于控制终端并且周期性地执行某种任务或等待处理某些事情的发生&#xff0c;主要表现为以下两个特点&#xff1a; 长期运行。 守…

Java中的多线程安全问题

目录 一、什么是线程安全&#xff1f; 二、线程不安全的原因 2.1 从底层剖析count的操作 2.2 线程不安全的原因总结 2.3 JVM内存模型&#xff08;JMM&#xff09; 三、synchronized 关键字-监视器锁monitor lock 3.1 如何加锁&#xff08;Synchronized用法和特性&#xff…

【sklearn】模型融合_堆叠法

Stacking参数含义1. 工具库 & 数据2. 定义交叉验证函数2.1 对融合模型2.2 对单个评估器3. 定义个体学习器和元学习器3.1 个体学习器3.2 元学习器4. 评估调整模型5. 元学习器的特征矩阵5.1 特征矩阵两个问题 & Stacking5.2 StackingClassfier\Regressor参数cv - 解决样本…

C语言 动态通讯录实现(附源码)

欢迎来到 Claffic 的博客 &#x1f49e;&#x1f49e;&#x1f49e; 前言&#xff1a; 上期博客写了静态通讯录并且答应给大家写一个动态版&#xff0c;这不&#xff0c;它来了&#xff1a; 1.动态版与静态版的区别 静态版的内存空间开辟大小是固定的&#xff0c;放了预期的最…

Leetcode 剑指 Offer II 010. 和为 k 的子数组

题目难度: 中等 原题链接 今天继续更新 Leetcode 的剑指 Offer&#xff08;专项突击版&#xff09;系列, 大家在公众号 算法精选 里回复 剑指offer2 就能看到该系列当前连载的所有文章了, 记得关注哦~ 题目描述 给定一个整数数组和一个整数 k &#xff0c;请找到该数组中和为…

MTBF是什么意思?交换机做MTBF有什么要求?MTTF、MTBF和MTTR的区别是什么?

MTBF&#xff0c;即平均故障间隔时间&#xff0c;英文全称是“Mean Time Between Failure”。是衡量一个交换机的可靠性指标。单位为“小时”。它反映了交换机的时间质量&#xff0c;是体现交换机在规定时间内保持功能的一种能力。具体来说&#xff0c;是指相邻两次故障之间的平…

【考研】2020-Part A 作文(英一)

可搭配以下链接一起学习&#xff1a; 【考研】2018-Part B 作文&#xff08;英一&#xff09;_住在阳光的心里的博客-CSDN博客 目录 一、2020 Part A &#xff08;一&#xff09;题目及解析 &#xff08;二&#xff09;优秀范文 &#xff08;三&#xff09;参考译文 &a…

Ansible playbook 讲解与实战操作

文章目录一、概述二、playbook 核心元素三、playbook 语法&#xff08;yaml&#xff09;1&#xff09;YAML 介绍1、YAML 格式如下2、playbooks yaml配置文件解释3、示例2&#xff09;variables 变量1、facts:可以直接调用2、用户自定义变量3、通过roles传递变量4、 Host Invent…

LINUX---文件

目录第一部分&#xff1a;文件编程一.打开/创建文件二.文件的写入操作三.文件的读取四.文件的光标应用&#xff1a;计算文件的大小第二部分&#xff1a;文件操作原理&#xff1a;一.文件描述符静态文件和动态文件第三部分&#xff1a;文件编程小应用1.实现CP命令2.修改文件3.写…

安卓玩机搞机技巧综合资源-----修改rom 制作rom 解包rom的一些问题解析【二十一】

接上篇 安卓玩机搞机技巧综合资源------如何提取手机分区 小米机型代码分享等等 【一】 安卓玩机搞机技巧综合资源------开机英文提示解决dm-verity corruption your device is corrupt. 设备内部报错 AB分区等等【二】 安卓玩机搞机技巧综合资源------EROFS分区格式 小米红…

【Vue笔记】Vue组件的创建、使用以及父子组件数据通信常见的几种方式

这篇文章&#xff0c;主要介绍Vue组件的创建、使用以及父子组件数据通信常见的几种方式。 目录 一、Vue组件的使用 1.1、局部组件 1.2、全局组件 1.3、动态组件&#xff08;组件动态切换&#xff09; 1.4、缓存组件 &#xff08;1&#xff09;如何缓存组件 &#xff08;…

微服务技术--Nacos与Eureka

eureka注册中心 远程调用的问题 消费者该如何获取服务提供者具体信息&#xff1f; 服务提供者启动时向eureka注册自己的信息eureka保存这些信息消费者根据服务名称向eureka拉取提供者信息 如果有多个服务提供者&#xff0c;消费者该如何选择&#xff1f; 服务消费者利用负载均…

区块链技术1---密码学基础

摘要&#xff1a;BTC属于加密货币&#xff0c;其中必然涉及到密码学的知识&#xff0c;而比特币比较开放&#xff0c;交易记录&#xff0c;交易金额甚至是底层源代码都是对外开放&#xff0c;那么加密使用在何处&#xff1f;这里就来谈一谈1&#xff1a;哈希哈希函数是密码学的…

client-go实战之六:时隔两年,刷新版本继续实战

欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码)&#xff1a;https://github.com/zq2599/blog_demos 系列文章链接 client-go实战之一&#xff1a;准备工作client-go实战之二:RESTClientclient-go实战之三&#xff1a;Clientsetclient-go实战之四&#xff1a;…

JavaWeb开发(三)3.3——Spring Bean详解

一、Bean的概念 由 Spring IoC 容器负责创建、管理所有的Java对象&#xff0c;这些管理的对象称为 Bean&#xff0c;Bean 根据 Spring 配置文件中的信息创建。 二、基于XML方式管理bean对象 eg&#xff1a; <?xml version"1.0" encoding"UTF-8"?&…

【B-树、B+树、B* 树】多叉平衡搜索树,解决“IO次数”与“树高”问题~

目录 一、为什么会出现B-树&#xff1f; 面试题&#xff1a; 二、什么是B-树&#xff1f; 2.1、B,B-树,B*树 导航 三、B-树的模拟实现 3.1、插入结点分析 3.1.1、根节点的分裂 3.1.2、继续插入数据&#xff0c;分裂子节点 3.2.3、再次插入数据&#xff0c;导致根节点继…