设计模式-策略模式-200

优点:用来消除 if-else、switch 等多重判断的代码,消除 if-else、switch 多重判断 可以有效应对代码的复杂性。
缺点:会增加类的数量,有的时候没必要为了消除几个if-else而增加很多类,尤其是那些类型又长又臭的
原始代码:

class Example {
    public static void main(String[] args) {
        discount("1",20D);
    }
    public static Double discount(String type, Double price) {
        // 策略模式
        if (Objects.equals(type, "1")) {
            return price * 0.95;
        }
        else if (Objects.equals(type, "2")){
            return price * 0.9;
        }
        return price;
    }
}

存在2个弊端:
1.if-else过多
2.不符合开闭原则。
开闭原则是对于扩展是开放的,但是对于修改是封闭的,这意味着一个实体是允许在不改变它的源代码的前提下变更它的行为。
策略模式优化大概是三个步骤:

  1. 定义抽象策略接口,因为业务使用接口而不是具体的实现类的话,便可以灵活的替换不同的策略;
  2. 定义具体策略实现类,实现自抽象策略接口,其内部封装具体的业务实现;
  3. 定义策略工厂,封装创建策略实现(算法),对客户端屏蔽具体的创建细节
    优化方法1:

interface DiscountStrategy {
    Double discount(Double price);
}
class DiscountStrategy1 implements DiscountStrategy{
    @Override
    public Double discount(Double price) {
        return price * 0.95;
    }
}

class DiscountStrategy2 implements DiscountStrategy{
    @Override
    public Double discount(Double price) {
        return price * 0.9;
    }
}
class DiscountStrategyFactory {
    private static final Map<String, DiscountStrategy> STRATEGY_MAP = new HashMap<>();
    static {
        STRATEGY_MAP.put("1", new DiscountStrategy1());
        STRATEGY_MAP.put("2", new DiscountStrategy2());
    }
    public static DiscountStrategy chooseStrategy(String type) {
        return STRATEGY_MAP.get(type);
    }
    public static void main(String[] args) {
        DiscountStrategy discountStrategy = chooseStrategy("1");
        System.out.println("Discount Price = "+discountStrategy.discount(10D));
    }
}

运行结果:
在这里插入图片描述
如果新增一个优惠策略,只需要新增一个策略算法实现类即可。但是,添加一个策略算法实现,意味着需要改动策略工厂中的代码,还是不符合开闭原则

如何完整实现符合开闭原则的策略模式,需要借助 Spring 的帮助,详细案例请继续往下看。

策略模式结合Spring

可以看到,比对上面的示例代码,有两处明显的变化:

  1. 抽象策略接口中,新定义了 mark() 接口,此接口用来标示算法的唯一性;
  2. 具体策略实现类,使用 @Component 修饰,将对象本身交由 Spring 进行管理
@Component
interface DiscountStrategy {
    Double discount(Double price);
    String mark();
}
@Component
class DiscountStrategy1 implements DiscountStrategy{
    @Override
    public Double discount(Double price) {
        return price * 0.95;
    }
    @Override
    public String mark() {
        return "1";
    }
}
@Component
class DiscountStrategy2 implements DiscountStrategy{
    @Override
    public Double discount(Double price) {
        return price * 0.9;
    }
    @Override
    public String mark() {
        return "2";
    }
}
@Component
class DiscountStrategyFactory implements InitializingBean {
    @Autowired
    private ApplicationContext applicationContext;

    private static final Map<String, DiscountStrategy> STRATEGY_MAP = new HashMap<>();

    public  DiscountStrategy chooseStrategy(String type) {
        return STRATEGY_MAP.get(type);
    }


    @Override
    public void afterPropertiesSet() throws Exception {
        Map<String, DiscountStrategy> beansOfType = applicationContext.getBeansOfType(DiscountStrategy.class);

//        v.mark()为策略对应的key,value为策略对象
        beansOfType.forEach((k, v) -> STRATEGY_MAP.put(v.mark(), v));
    }


}
@SpringBootTest
class Main {
    @Autowired DiscountStrategyFactory discountStrategyFactory;
    @Test
    void contextLoads() {
        DiscountStrategy discountStrategy = discountStrategyFactory.chooseStrategy("1");
        System.out.println("Discount Price = "+ discountStrategy.discount(10D));
    }
}

通过 InitializingBean 接口实现中调用 IOC 容器查找对应策略实现,随后将策略实现 mark() 方法返回值作为 key, 策略实现本身作为 value 添加到 Map 容器中等待客户端的调用。就是把Interface的所有实现类都弄到Map里面了,通过forEach方法!(前提是实现类有@Component,没有的话是注册不上的!!)
这里使用的 SpringBoot 测试类,注入策略工厂 Bean,通过策略工厂选择出具体的策略算法类,继而通过算法获取到优惠后的价格。小插曲:如果不想把策略工厂注入 Spring 也可以,实现方法有很多。

总结下本小节,我们通过和 Spring 结合的方式,通过策略设计模式对文初的代码块进行了两块优化:应对代码的复杂性,让其满足开闭原则。更具体一些呢就是 通过抽象策略算法类减少代码的复杂性,继而通过 Spring 的一些特性同时满足了开闭原则,现在来了新需求只要添加新的策略类即可,健壮易扩展。

策略模式的本质依然是对代码设计解耦合,通过三类角色贯穿整个策略模式:抽象策略接口策略工厂以及具体的策略实现类。通过细粒度的策略实现类避免了主体代码量过多,减少了设计中的复杂性

出现 if-else 的代码,一定要使用策略模式优化么?
如果 if-else 判断分支不多并且是固定的,后续不会出现新的分支,那我们完全 可以通过抽函数的方式降低程序复杂性;不要想法设法去除 if-else 语句,存在即合理。而且,使用策略模式会导致类增多,没有必要为了少量的判断分支引入策略模式。

扩展

当业务使用越来越多的情况下,重复定义 DiscountStrategy 以及 DiscountStrategyFactory 会增加系统冗余代码量。
可以考虑将这两个基础类抽象出来,作为基础组件库中的通用组件,供所有系统下的业务使用,从而避免代码冗余。
定义抽象策略处理接口,添加有返回值和无返回值接口。

package org.opengoofy.congomall.springboot.starter.designpattern.strategy;

/**
 * 策略执行抽象
 */
public interface AbstractExecuteStrategy<REQUEST, RESPONSE> {

    /**
     * 执行策略标识
     */
    String mark();

    /**
     * 执行策略
     *
     * @param requestParam 执行策略入参
     */
    default void execute(REQUEST requestParam) {

    }

    /**
     * 执行策略,带返回值
     *
     * @param requestParam 执行策略入参
     * @return 执行策略后返回值
     */
    default RESPONSE executeResp(REQUEST requestParam) {
        return null;
    }
}

添加策略选择器,通过订阅 Spring 初始化事件执行扫描所有策略模式接口执行器,并根据 mark 方法定义标识添加到 abstractExecuteStrategyMap 容器中。
客户端在实际业务中使用 AbstractStrategyChoose#choose 即可完成策略模式实现。

package org.opengoofy.congomall.springboot.starter.designpattern.strategy;

import org.opengoofy.congomall.springboot.starter.base.ApplicationContextHolder;
import org.opengoofy.congomall.springboot.starter.base.init.ApplicationInitializingEvent;
import org.opengoofy.congomall.springboot.starter.convention.exception.ServiceException;
import org.springframework.context.ApplicationListener;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

/**
 * 策略选择器
 */
public class AbstractStrategyChoose implements ApplicationListener<ApplicationInitializingEvent> {

    /**
     * 执行策略集合
     */
    private final Map<String, AbstractExecuteStrategy> abstractExecuteStrategyMap = new HashMap<>();

    /**
     * 根据 mark 查询具体策略
     *
     * @param mark 策略标识
     * @return 实际执行策略
     */
    public AbstractExecuteStrategy choose(String mark) {
        return Optional.ofNullable(abstractExecuteStrategyMap.get(mark)).orElseThrow(() -> new ServiceException(String.format("[%s] 策略未定义", mark)));
    }

    /**
     * 根据 mark 查询具体策略并执行
     *
     * @param mark         策略标识
     * @param requestParam 执行策略入参
     * @param <REQUEST>    执行策略入参范型
     */
    public <REQUEST> void chooseAndExecute(String mark, REQUEST requestParam) {
        AbstractExecuteStrategy executeStrategy = choose(mark);
        executeStrategy.execute(requestParam);
    }

    /**
     * 根据 mark 查询具体策略并执行,带返回结果
     *
     * @param mark         策略标识
     * @param requestParam 执行策略入参
     * @param <REQUEST>    执行策略入参范型
     * @param <RESPONSE>   执行策略出参范型
     * @return
     */
    public <REQUEST, RESPONSE> RESPONSE chooseAndExecuteResp(String mark, REQUEST requestParam) {
        AbstractExecuteStrategy executeStrategy = choose(mark);
        return (RESPONSE) executeStrategy.executeResp(requestParam);
    }

    @Override
    public void onApplicationEvent(ApplicationInitializingEvent event) {
        Map<String, AbstractExecuteStrategy> actual = ApplicationContextHolder.getBeansOfType(AbstractExecuteStrategy.class);
        actual.forEach((beanName, bean) -> {
            AbstractExecuteStrategy beanExist = abstractExecuteStrategyMap.get(bean.mark());
            if (beanExist != null) {
                throw new ServiceException(String.format("[%s] Duplicate execution policy", bean.mark()));
            }
            abstractExecuteStrategyMap.put(bean.mark(), bean);
        });
    }
}

这两个实现已经被放置在基础组件库中,如果业务需要使用策略模式,则无需重新定义。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于vue框架的大学生四六级学习网站设计与实现i8o8z(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;学生,训练听力,学习单词,单词分类,阅读文章,文章类型,学习课程 开题报告内容 基于Vue框架的大学生四六级学习网站设计与实现开题报告 一、研究背景与意义 随着全球化进程的加速和国际交流的日益频繁&#xff0c;英语作为国际通用语言…

【前端开发入门】html快速入门

目录 引言一、html基础模板内容二、html文档流三、html 标签1.块级元素2.行内元素3.功能性元素4.标签嵌套 四、html编码习惯五、总结 引言 本系列教程旨在帮助一些零基础的玩家快速上手前端开发。基于我自学的经验会删减部分使用频率不高的内容&#xff0c;并不代表这部分内容不…

vue.js 原生js app端实现图片旋转、放大、缩小、拖拽

效果图&#xff1a; 旋转 放大&#xff1a;手机上可以双指放大缩小 拖拽 代码实现&#xff1a; html <div id"home" class"" v-cloak><!-- 上面三个按钮 图片自己解决 --><div class"headImage" v-if"showBtn">&l…

数据订阅与消费中间件Canal 服务搭建(docker)

MySQL Bin-log开启 进入mysql容器 docker exec -it mysql5.7 bash开启mysql的binlog cd /etc/mysql/mysql.conf.dvi mysqld.cnf #在文件末尾处添加如下配置&#xff08;如果没有这个文件就创建一个&#xff09; [mysqld] # 开启 binlog log-binmysql-bin #log-bin/var/lib/mys…

Linux集群部署RabbitMQ

目录 一、准备三台虚拟机&#xff0c;配置相同 1、所有主机都需要hosts文件解析 2、所有主机安装erLang和rabbitmq 3、修改配置文件 4、导入rabbitmq 的管理界面 5、查看节点状态 6、设置erlang运行节点 7、rabitmq2和rabbitmq3重启服务 8、查看各个节点状态 二、添加…

LC刷题专题:二叉树;迭代;递归(897、1372、208)

文章目录 897.递增顺序搜索树1372. 二叉树中的最长交错路径208. 实现 Trie (前缀树) 897.递增顺序搜索树 https://leetcode.cn/problems/increasing-order-search-tree/description/ 这道题目本身就是一个简单题&#xff1a;非常容易实现&#xff1a;只需要在递归或者迭代中序…

基于spring boot的篮球论坛系统

作者&#xff1a;计算机搬砖家 开发技术&#xff1a;SpringBoot、php、Python、小程序、SSM、Vue、MySQL、JSP、ElementUI等&#xff0c;“文末源码”。 专栏推荐&#xff1a;SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;Java精选实战项…

每日一练:地下城游戏

174. 地下城游戏 - 力扣&#xff08;LeetCode&#xff09; 题目要求&#xff1a; 恶魔们抓住了公主并将她关在了地下城 dungeon 的 右下角 。地下城是由 m x n 个房间组成的二维网格。我们英勇的骑士最初被安置在 左上角 的房间里&#xff0c;他必须穿过地下城并通过对抗恶魔…

telnet发送邮件教程:安全配置与操作指南?

telnet发送邮件的详细步骤&#xff1f;怎么用telnet命令发邮件&#xff1f; 尽管现代邮件客户端和服务器提供了丰富的功能和安全性保障&#xff0c;但在某些特定场景下&#xff0c;了解如何使用telnet发送邮件仍然是一项有价值的技能。AokSend将详细介绍如何安全配置和操作tel…

数字电路week1

数字电路学习 一.电路基础 1.数字电路仿真软件-digital digital官网&#xff1a;https://github.com/hneemann/Digital?tabreadme-ov-file 建议使用红绿色盲模式 2.异或门 输入1输入2输出000011101110 简单来说就是判断两个输入是否相同 异或门的组成&#xff1a; 异或…

C语言 动态数据结构的C语言实现内存映像

C程序的内存映像 C程序中变量的内存分配方式  C程序中变量的内存分配方式  从静态存储区分配  全局变量和静态变量 C程序中变量的内存分配方式  从静态存储区分配  全局变量和静态变量  在栈上分配  存放函数参数值&#xff0c;局部变量值等  …

国外电商系统开发-运维系统功能清单开发

一、最终效果图 二、功能清单 功能 描述 自定义日志绘图 根据Nginx、Apache登录日志文件绘图&#xff0c;绘图数据包括&#xff1a;访问量走势&#xff0c;500错误&#xff0c;200正确百分比等 创建服务器 加入服务器 主机状态自动检查 加入主机到系统后&#xff0c;系统…

机器学习:opencv--摄像头OCR

目录 前言 一、三个函数 1.显示图像 2.点排序 3.透视变换 二、代码实例 1.打开摄像头 2.图像预处理 3.检测特定轮廓 4.对轮廓进行处理 5.释放资源 前言 摄像头OCR指的是利用摄像头捕捉图像中的文字信息&#xff0c;并通过光学字符识别&#xff08;OCR&#xff09;技…

深度学习----------------------------编码器、解码器架构

目录 重新考察CNN重新考察RNN编码器-解码器架构总结编码器解码器架构编码器解码器合并编码器和解码器 重新考察CNN 编码器&#xff1a;将输入编码成中间表达形式&#xff08;特征&#xff09; 解码器&#xff1a;将中间表示解码成输出。 重新考察RNN 编码器&#xff1a;将文…

CSS 盒子属性

1. 盒子模型组成 1.1 边框属性 1.1.1 四边分开写 1.1.2 合并线框 1.1.3 边框影响盒子大小 1.2 内边距 注意&#xff1a; 1.3 外边距 1.3.1 嵌套块元素垂直外边距的塌陷 1.4 清除内外边距 1.5 总结

EasyExcel使用介绍

EasyExcel使用 1、EasyExcel介绍 1.1 官网介绍 传统操作Excel大多都是利用Apach POI进行操作的&#xff0c;但是POI框架并不完善&#xff0c;使用过程非常繁琐且有较多的缺陷&#xff1a; 动态操作Excel非常繁琐,对于新手来说&#xff0c;很难在短时间内上手;读写时需要占用…

二叉树深度学习——将二叉搜索树转化为排序的双向链表

1.题目解析 题目来源&#xff1a;LCR 155.将二叉搜索树转化为排序的双向链表 测试用例 2.算法原理 首先题目要求原地进行修改并且要求左指针代表前驱指针&#xff0c;右指针代表后继指针&#xff0c;所以思路就是 1.使用前序遍历创建两个指针cur、prev代表当前节点与前一个节点…

Stable Diffusion绘画 | 来训练属于自己的模型:炼丹参数调整--步数设置与计算

要想训练一个优质的模型&#xff0c;一定要认识和了解模型训练中&#xff0c;参数的作用和意义。 整个模型训练的过程&#xff0c;参数并不是一成不变的&#xff0c;也没有固定的模板&#xff0c; 当我们修改了模型训练里面的某个参数&#xff0c;很可能就需要连带其他一系列…

在LabVIEW中如何读取EXCEL

在LabVIEW中读取Excel文件通常使用“报告生成工具包”&#xff08;Report Generation Toolkit&#xff09;。以下是详细步骤&#xff1a; ​ 安装工具包&#xff1a;确保已安装“报告生成工具包”。这通常随LabVIEW一起提供&#xff0c;但需要单独安装。 创建VI&#xff1a; 打…

java入门基础(一篇搞懂)

​ 如果您觉得这篇文章对您有帮助的话 欢迎您分享给更多人哦 感谢大家的点赞收藏评论&#xff0c;感谢您的支持&#xff01;&#xff01;&#xff01; 首先给大家推荐比特博哥&#xff0c;java入门安装的JDk和IDEA社区版的安装视频 JDK安装与环境变量的配置 IDEA社区的安装与使…