设计模式-策略模式-200

news2024/12/21 23:50:11

优点:用来消除 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.coloradmin.cn/o/2183655.html

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

相关文章

scratch棒球运动 2024年9月中国电子学会图形化编程 少儿编程 scratch编程等级考试一级真题和答案解析

目录 scratch棒球运动 一、题目要求 1、准备工作 2、功能实现 二、案例分析 1、角色分析 2、背景分析 3、前期准备 三、解题思路 1、思路分析 2、详细过程 四、程序编写 五、考点分析 六、 推荐资料 1、入门基础 2、蓝桥杯比赛 3、考级资料 4、视频课程 5、…

进程--信号量

信号量是什么 资源的竞争 资源竞争 : 当多个进程同时访问共享资源时&#xff0c;会产生资源竞争&#xff0c;最终最导致数据混乱临界资源 : 不允许同时有多个进程访问的资源&#xff0c;包括硬件资源(CPU、内存、存储器以及其他外围设备)与软件资源(共享代码段、共享数据结构…

SpringCloudEureka实战:搭建EurekaServer

1、依赖引入 <dependencies><!-- 注册中心 --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-server</artifactId></dependency> </dependencies> <de…

66.对cplusplus网的strtok函数的详细解释(补第56篇的翻译)

56.【C语言】字符函数和字符串函数(strtok函数) 点我跳转 目录 56.【C语言】字符函数和字符串函数(strtok函数) 点我跳转 1.原文 2. 翻译 1.原文 原文链接: cplusplus的介绍 点我跳转 2. 翻译 函数 strtok char * strtok ( char * str, const char * delimiters ); Spli…

如何构建一个生产级的AI平台(3)?

书接上回&#xff0c;继续往下讲,本节会说一下模型的路由和网关 模型的路由和网关 随着应用程序复杂性的增加和涉及的模型越来越多&#xff0c; 出现了两种类型的工具来帮助使用多个模型&#xff1a;路由和网关 1. 路由 应用程序可以使用不同的模型来响应不同类型的查询。 …

平衡二叉搜索树删除的实现

前言 上期讲了平衡二叉搜索树的插入&#xff0c;这一期我们来讲讲删除。同时&#xff0c;二叉搜索树的简介不会出现在本篇博客之中&#xff0c;如有需要可以查看上一篇博客《平衡二叉搜索树插入的实现》。 平衡二叉搜索树插入的实现-CSDN博客文章浏览阅读659次&#xff0c;点赞…

三、I/O控制器

1.主要功能 接受和识别CPU发出的命令(要有控制寄存器) 向CPU报告设备的状态(要有状态寄存器) 数据交换(要有数据寄存器&#xff0c;暂存输入/输出的数据) 地址识别(由I/0逻辑实现) 2.组成 CPU与控制器之间的接口(实现控制器与CPU之间的通信) I/0逻辑(负责识别CPU发出的命…

鸿蒙NEXT开发-ArkUI(基于最新api12稳定版)

注意&#xff1a;博主有个鸿蒙专栏&#xff0c;里面从上到下有关于鸿蒙next的教学文档&#xff0c;大家感兴趣可以学习下 如果大家觉得博主文章写的好的话&#xff0c;可以点下关注&#xff0c;博主会一直更新鸿蒙next相关知识 专栏地址: https://blog.csdn.net/qq_56760790/…

自定义注解加 AOP 实现服务接口鉴权以及内部认证

注解 何谓注解&#xff1f; 在Java中&#xff0c;注解&#xff08;Annotation&#xff09;是一种特殊的语法&#xff0c;用符号开头&#xff0c;是 Java5 开始引入的新特性&#xff0c;可以看作是一种特殊的注释&#xff0c;主要用于修饰类、方法或者变量&#xff0c;提供某些信…

中英翻译神器!轻松搞定跨文化沟通

大家好&#xff01;今天咱们来聊聊那些你生活中不可或缺的翻译小助手&#xff1b;不论你是个英语小白&#xff0c;还是希望更快地了解外国文献、掌握外媒信息&#xff0c;或者是从事需要大量翻译工作的小伙伴&#xff0c;总有一款翻译工具能帮你省时省力&#xff0c;让你的生活…

DBCP数据库连接池以及在Tomcat中配置JNDI数据源

前言 数据库连接 数据库连接是指在计算机系统中建立起应用程序与数据库之间的连接通道&#xff0c;用于进行数据的读取和写入操作。通过数据库连接&#xff0c;应用程序可以与数据库进行交互&#xff0c;执行各种数据库操作&#xff0c;如查询数据、插入数据、更新数据和删除数…

算法题总结(四)——螺旋矩阵

螺旋矩阵 59、螺旋矩阵二 给你一个正整数 n &#xff0c;生成一个包含 1 到 n2 所有元素&#xff0c;且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;[[1,2,3],[8,9,4],[7,6,5]]示例 2&#xff1a; 输…

2.点位管理开发(续)及设计思路——帝可得后台管理系统

目录 前言一、页面原型二、修改1、页面展示2、新增 3 、总结思路 前言 提示&#xff1a;本篇继续点位管理的改造 一、页面原型 页面展示新增 二、修改 1、页面展示 页面修改&#xff1a;修改标签换行、顺序顺序、地址过长时换行问题&#xff1b; <el-table v-loading…

七,MyBatis-Plus 扩展功能:乐观锁,代码生成器,执行SQL分析打印(实操详细使用)

七&#xff0c;MyBatis-Plus 扩展功能&#xff1a;乐观锁&#xff0c;代码生成器&#xff0c;执行SQL分析打印&#xff08;实操详细使用&#xff09; 文章目录 七&#xff0c;MyBatis-Plus 扩展功能&#xff1a;乐观锁&#xff0c;代码生成器&#xff0c;执行SQL分析打印&#…

愿祖国富强!肌肉水凝胶的奥秘,自协调与光驱动,运动模式大揭秘

大家好&#xff0c;在这个国庆佳节&#xff0c;我们一同感受科技的魅力。今天来了解一种特殊的肌肉样水凝胶&#xff0c;它通过自协调形状变化和摩擦调节&#xff0c;能在光的引导下实现多样运动。这一成果为软机器人发展带来新契机——《Light-steered locomotion of muscle-l…

基于ScriptableObject设计游戏数据表

前言 本篇文章是针对之前对于ScriptableObject概念讲解的实际应用之一&#xff0c;在游戏开发中&#xff0c;我们可以使用该类来设计编辑器时的可读写数据表或者运行时的只读数据表。本文将针对运行时的只读数据表的应用进行探索&#xff0c;并且结合自定义的本地持久化存储方式…

一级建造师备考攻略及一建各科老师推荐(各科四大金刚)

吐血整理:真正的实战派一建名师推荐! 考过的同学一定都知道推荐的老师YYDS! 一级建造师各科老师推荐: 《法规》名师:王欣、王竹梅、陈印、关涛 其他老师:房超、蔡恒、刘丹、武海峰、陈洁、安国庆、桂林 《管理》名师:宿吉南、龙炎飞、李向国、朱俊文 其他老师:缴广才、陈晨…

跟《经济学人》学英文:2024年09月28日这期 The curse of the Michelin star

The curse of the Michelin star Restaurants awarded the honour are more likely to close, research finds 原文&#xff1a; The twelve new restaurants added to the New York Michelin Guide this month, serving up cuisine ranging from “haute French” to “eco…

9.数据结构与算法-单链表,循环链表和双向链表的比较////顺序表和链表的比较

单链表&#xff0c;循环链表和双向链表的时间效率比较 顺序表和链表的区别 存储密度

HarmonyOS Next应用开发——自定义组件的使用

自定义组件的使用 在ArkUI中&#xff0c;UI显示的内容均为组件&#xff0c;由框架直接提供的称为系统组件&#xff0c;由开发者定义的称为自定义组件。在进行 UI 界面开发时&#xff0c;通常不是简单的将系统组件进行组合使用&#xff0c;而是需要考虑代码可复用性、业务逻辑与…