设计模式-行为型模式-策略模式

一、什么是策略模式

        策略模式是一种行为设计模式,它允许在运行时选择算法或行为,并将其封装成独立的对象,使得这些算法或行为可以相互替换,而不影响使用它们的客户端。(ChatGPT生成)

        主要组成部分:

        1、策略(Strategy): 定义了一个算法族、行为或方法,并将其封装在一个接口或抽象类中,使得这些算法可以相互替换。

        2、具体策略(Concrete Strategy): 实现了策略接口,提供了具体的算法或行为实现。

        3、上下文(Context): 持有一个策略对象的引用,在运行时可动态切换不同的策略。

        原理:

        1、策略模式允许在运行时动态选择算法或行为,通过将具体的算法或行为封装成策略对象,然后将这些策略对象注入到上下文中。

        2、上下文根据不同的需求或条件选择合适的策略对象,并使用它执行特定的算法或行为。

        优点:

        1、易于扩展和维护: 可以方便地添加新的策略或修改现有策略,不影响原有代码。

        2、避免条件语句: 可以避免大量的条件语句,提高代码可读性和可维护性。

        3、代码复用: 可以通过共享策略对象来提高代码的复用性。

二、场景模拟

        为了应用策略模式,我们模拟一个场景。在一个企业资源管理(ERP)系统中,可能会涉及到成本的计算,其中需要计算的成本例如:钢材、油漆、人工、运输等等,虽然说计算成本都是“单价*数量”,但是有些成本,例如钢材,不仅要计算钢材的成本,而且放置钢材或者其它费用,也需要进行一定的计费;对于人工成本,还需要计算企业担负的五险一金等。

 如上图所示,钢材是由吨数*单价计算的,但是要考虑保管费或其他费用,假设一吨n元,后续还可能考虑其它费用等;人工费用,雇员一人可能工资12000元,公司要负担其12%公积金或者医疗保险等;运输要考虑车辆维护费或者日常损耗。(以上仅是模拟,真实环境可能更加复杂或简单)。

三、业务实现

3.1、使用IF-ELSE方法

public static BigDecimal useIfElse(String name, BigDecimal amount, BigDecimal price){

        if (name.equals("plate")) {
            // 单价×数量,再加上每吨200元的其它费用
            return amount.multiply(price).add(amount.multiply(new BigDecimal("200.00")));
        } else if (name.equals("paints")) {
            // 单价×数量,再加上每吨200元的其它费用
            return amount.multiply(price).add(amount.multiply(new BigDecimal("100.00")));
        } else if (name.equals("employer")) {
            // 人员×数量,再加上每吨200元的其它费用
            BigDecimal sum = amount.multiply(price); // 基本工资
            sum = sum.add(amount.multiply(price.multiply(new BigDecimal("0.12")))); // 加上额外支付的12%公积金
            return sum;
        } else if (name.equals("haulage")) {
            // 吨数×数量,再加上每吨300元的损耗费用
            return amount.multiply(price).add(amount.multiply(new BigDecimal("300.00")));
        } else {
            log.error("无法匹配");
            return new BigDecimal(0);
        }

    }

我们用IF-ELSE方法,确实是可以实现功能,可以看出来很麻烦,看起来很混乱,没有结构,也没办法实现一些定制功能,后续变动起来也比骄困难,因为有一些东西都是写死的。我们来测试一下:

public static void main(String[] args) {

        // 钢板单价及数量
        BigDecimal plateAmount = new BigDecimal(15);
        BigDecimal platePrice = new BigDecimal("888.88");

        // 油漆价格及数量
        BigDecimal paintsAmount = new BigDecimal(20);
        BigDecimal paintsPrice = new BigDecimal("490.00");

        // 雇员工资及人数
        BigDecimal employerAmount = new BigDecimal(10);
        BigDecimal employerPrice = new BigDecimal("10000.00");

        // 运输价格及吨数
        BigDecimal haulageAmount = new BigDecimal(25);
        BigDecimal haulagePrice = new BigDecimal("3000.00");

        // 计算各个成本
        BigDecimal plate = useIfElse("plate", plateAmount, platePrice);
        BigDecimal paints = useIfElse("paints", paintsAmount, paintsPrice);
        BigDecimal employer = useIfElse("employer", employerAmount, employerPrice);
        BigDecimal haulage = useIfElse("haulage", haulageAmount, haulagePrice);

        BigDecimal sum = plate.add(paints).add(employer).add(haulage);

        log.info("钢材:{}元,油漆{}元,雇员:{}元,运输:{}元,总计:{}元", plate, paints, employer, haulage, sum);

    }

可以看到确实正确的计算出了结果,但是这种方式不具有拓展性,随着程序功能的增加,会越来越混乱。那有些同学可能会说,为什么不用Switch-Case方法?那就来实践一下。

3.2、使用Switch-Case方法

public static BigDecimal useSwitch(String name, BigDecimal amount, BigDecimal price){

        switch (name) {
            case "plate":
                // 单价×数量,再加上每吨200元的其它费用
                return amount.multiply(price).add(amount.multiply(new BigDecimal("200.00")));
            case "paints":
                // 单价×数量,再加上每吨200元的其它费用
                return amount.multiply(price).add(amount.multiply(new BigDecimal("100.00")));
            case "employer":
                // 人员×数量,再加上每吨200元的其它费用
                BigDecimal sum = amount.multiply(price); // 基本工资
                sum = sum.add(amount.multiply(price.multiply(new BigDecimal("0.12")))); // 加上额外支付的12%公积金
                return sum;
            case "haulage":
                // 吨数×数量,再加上每吨300元的损耗费用
                return amount.multiply(price).add(amount.multiply(new BigDecimal("300.00")));
            default:
                log.error("无法匹配");
                return new BigDecimal(0);
        }

    }

使用Switch-Case方法后,确实代码变得比较清晰了,但是一些东西仍然是写死的,不具备很好的扩展性。

3.3、使用策略模式重构代码

        其中ICosts是一个接口,里面只有一个方法,就是成本的计算。他的实现类包含钢材、油漆、人工等等,可以有很多。Context是策略控制类,外部可以传递不同策略执行计算成本方法。

 3.3.1、编写成本计算接口

public interface ICosts<T, K, V> {

    /**
     * 成本计算
     * @param amount 数量
     * @param price 单价
     * @param other 其它信息
     * @return 成本金额
     */
    BigDecimal calculateCosts(T amount, BigDecimal price, Map<K, V> other);
}

 解释一下amount为什么要用T表示,因为这里数量不仅仅是BigDecimal形式的,可能会涉及到人、车辆等等,这种是没有小数点的……;关于other就是一些附加信息,例如钢材的除锈费用、维护费用,人员的五险一金比例等等。

3.3.2、具体成本实现类

钢材

public class GCCosts implements ICosts<BigDecimal, String, BigDecimal> {

    /*
    * 钢材成本
    *   1、基本成本:吨数 * 价格;
    *   2、存放成本:吨数 * 存放消费;
    *   3、防锈成本:吨数 * 防锈成本;
    * */
    @Override
    public BigDecimal calculateCosts(BigDecimal amount, BigDecimal price, Map<String, BigDecimal> other) {

        BigDecimal sum = amount.multiply(price);

        // 计算存放成本
        if (other.containsKey("CF")) {
            sum = sum.add(amount.multiply(other.get("CF")));
        }
        // 计算防锈成本
        if (other.containsKey("CX")) {
            sum = sum.add(amount.multiply(other.get("CX")));
        }

        return sum;
    }
}

油漆

public class YQCosts implements ICosts<BigDecimal, String, BigDecimal> {

    /*
    * 油漆成本
    *   1、基础成本:单价 * 数量;
    *   2、存放成本: 数量 * 存放费用;
    * */
    @Override
    public BigDecimal calculateCosts(BigDecimal amount, BigDecimal price, Map<String, BigDecimal> other) {

        BigDecimal sum = amount.multiply(price);

        if (other.containsKey("CF")) {
            sum = sum.add(amount.multiply(other.get("CF")));
        }

        return sum;
    }
}

人工

public class RGCosts implements ICosts<Long, String, BigDecimal> {

    /*
    * 人工成本:
    *   1、基础成本:人数 * 工资;
    *   2、公积金成本: 人数 * (工资 * 公积金比例);
    *   3、五险成本:人数 * (工资 * 企业分摊比例);
    * */
    @Override
    public BigDecimal calculateCosts(Long amount, BigDecimal price, Map<String, BigDecimal> other) {

        // 先计算每个人成本
        BigDecimal personCosts = price;

        // 加公积金
        if (other.containsKey("GJJ")) {
            personCosts = personCosts.add(price.multiply(other.get("GJJ")));
        }

        // 加五险
        if (other.containsKey("BX")) {
            personCosts = personCosts.add(price.multiply(other.get("BX")));
        }

        return personCosts.multiply(BigDecimal.valueOf(amount)); // 计算总成本
    }
}

运输

public class YSCosts implements ICosts<BigDecimal, String, BigDecimal> {

    /*
    * 运输成本:
    *   1、基础成本:运输数量 * 运输单价;
    *   2、损耗费用:例如换机油,养护等费用,固定金额(模拟);
    * */
    @Override
    public BigDecimal calculateCosts(BigDecimal amount, BigDecimal price, Map<String, BigDecimal> other) {

        BigDecimal sum = amount.multiply(price);

        // 加上固定的养护费用
        if (other.containsKey("YH")) {
            sum = sum.add(other.get("YH"));
        }

        return sum;
    }
}

3.3.3、成本策略控制类

public class CostsContext<T, K, V>{

    private ICosts<T, K, V> iCosts;

    public CostsContext(ICosts<T, K, V> iCosts) {
        this.iCosts = iCosts;
    }

    public BigDecimal calculateCosts(T amount, BigDecimal price, Map<K, V> other) {
        return iCosts.calculateCosts(amount, price, other);
    }
}

此类充当一个调度作用,不同的成本计算就选择不同的实现类,然后执行calculateCosts()方法。

3.3.4、测试

public static void main(String[] args) {

        // 钢板单价及数量
        BigDecimal plateAmount = new BigDecimal(15);
        BigDecimal platePrice = new BigDecimal("888.88");
        Map<String, BigDecimal> plateMap = new HashMap<String, BigDecimal>(){{
            put("CF", new BigDecimal("300"));
            put("CX", new BigDecimal("200"));
        }};

        // 油漆价格及数量
        BigDecimal paintsAmount = new BigDecimal(20);
        BigDecimal paintsPrice = new BigDecimal("490.00");
        Map<String, BigDecimal> paintsMap = new HashMap<String, BigDecimal>(){{
            put("CF", new BigDecimal("300"));
        }};

        // 雇员工资及人数
        // BigDecimal employerAmount = new BigDecimal(10);
        Long employerAmount = 10L;
        BigDecimal employerPrice = new BigDecimal("10000.00");
        Map<String, BigDecimal> employerMap = new HashMap<String, BigDecimal>(){{
            put("GJJ", new BigDecimal("0.12"));
            put("BX", new BigDecimal("0.06"));
        }};

        // 运输价格及吨数
        BigDecimal haulageAmount = new BigDecimal(25);
        BigDecimal haulagePrice = new BigDecimal("3000.00");
        Map<String, BigDecimal> haulageMap = new HashMap<String, BigDecimal>(){{
            put("YH", new BigDecimal("1500"));
        }};

        // 使用策略模式计算
        // 钢材
        CostsContext<BigDecimal, String, BigDecimal> plateCost = new CostsContext<>(new GCCosts());
        BigDecimal plate = plateCost.calculateCosts(plateAmount, platePrice, plateMap);

        // 油漆
        CostsContext<BigDecimal, String, BigDecimal> paintsCost = new CostsContext<>(new YQCosts());
        BigDecimal paints = paintsCost.calculateCosts(paintsAmount, paintsPrice, paintsMap);

        // 人工
        CostsContext<Long, String, BigDecimal> employerCost = new CostsContext<>(new RGCosts());
        BigDecimal employer = employerCost.calculateCosts(employerAmount, employerPrice, employerMap);

        // 运输
        CostsContext<BigDecimal, String, BigDecimal> haulageCost = new CostsContext<>(new YQCosts());
        BigDecimal haulage = haulageCost.calculateCosts(haulageAmount, haulagePrice, haulageMap);

        BigDecimal sum = plate.add(paints).add(employer).add(haulage);

        log.info("钢材:{}元,油漆{}元,雇员:{}元,运输:{}元,总计:{}元", plate, paints, employer, haulage, sum);

    }

上面是一些各种成本的数量和单价信息,同时使用Map存放了一些特有的成本信息,然后下面我们使用成本策略控制类CostsContext装载不同的实现类来进行成本的计算。

可以看到成功的计算出了结果。我们不仅实现了功能,也为后续的扩展打下了良好的基础,例如后续加新的成本类型进行计算,我们只需要继承ICosts实现不同的方法即可,如果某一个类型的计算加了一些条件,这样我们直接在Map里面添加条件,并在相应的实体类进行处理即可,这样让我们的代码结构变得清晰。

四、小结 

        if-else是我们初学代码时就接触到的代码,伴随我们走过了很多年,但是我们的思维一定要有提高,不要一有需求,就想到if-else,确实,if-else可以快速地实现功能,我们不可否认,但是随着项目复杂度的提高,if-else已经无法满足我们的需求,它会让我们的代码变得臃肿、复杂、结构混乱,所以我们就想到要用其它方法来替代if-else,让我们整个代码结构变得清晰。

        上面我们介绍了使用策略模式来代替一堆的if-else,策略模式的优点远不止如此:

        1、具有强的扩展性,例如我们扩充新的成本计算,只需要新建类并实现接口即可;

        2、易维护和理解,如何我么要看或者修改钢材的成本计算,我们不用到一堆if-else里面去找具体的代码块,我们只需要进去相应的实现类去看就可以,代码清晰明了,没有其他代码的干扰(体现出隔离性),我们维护起来也得心应手;

        3、使用策略模式可以让我们代码变得优美,思维能力和架构水平得到提升。

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

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

相关文章

数据结构与算法-图

图 &#x1f388;2.图的存储结构&#x1f4d6;2.4.2邻接表的存储✅2.4.2.1逆邻接表✅2.4.2.2邻接表存储结构的定义✅2.4.2.3邻接表存储结构的类定义✅2.4.2.4创建n个顶点m条边的无向网✅2.4.2.5创建n个顶点m条边的有向网✅2.4.2.6定位操作-查找定点信息在顶点数组中的下标✅2.4…

Spring Boot中使用Redis进行大数据缓存

Spring Boot中使用Redis进行大数据缓存 在Spring Boot中使用Redis进行大数据缓存是一种常见的做法&#xff0c;因为Redis是一种高性能的内存数据库&#xff0c;适用于缓存大量数据。以下是说明和示例代码&#xff0c;演示如何在Spring Boot项目中使用Redis进行大数据缓存。 步…

C语言基础篇4:变量、存储、库函数

1 局部变量和全局变量 在介绍局部变量和全局变量前&#xff0c;先了解一些关于作用域方面的内容。作用域的作用就是决定程序中的哪些语句是可用的&#xff0c;换句话说&#xff0c;就是程序中的可见性。作用域有局部作用域和全局作用域&#xff0c;那么局部变量就具有局部作用域…

3DMAX森林树木植物插件ForestPackLite教程

3DMAX森林树木植物插件ForestPackLite教程 Forest Pack是世界上最受欢迎的散布插件。它提供了一个完整的解决方案来创建大面积的物体&#xff0c;从树木和植物到建筑、人群、骨料、地面覆盖物、岩石等等。如果你能为它建模&#xff0c;森林包就能把它分散开来。 无数工作室依靠…

安装Nginx时报:./configure: error: can not define uint32_t

安装版本&#xff1a;v.1.25.3 安装命令&#xff1a; ./configure --prefix/opt/nginx/1.25.3 && make && make install 执行命令后出现异常&#xff0c;如图&#xff1a; 解决方法&#xff1a; yum install -y kernel-headers kernel-devel gcc make

算数通关村第十一关-白银挑战位运算高频题

位移的运算 位1的个数 描述 : 编写一个函数&#xff0c;输入是一个无符号整数&#xff08;以二进制串的形式&#xff09;&#xff0c;返回其二进制表达式中数字位数为 1 的个数&#xff08;也被称为汉明重量&#xff09;。 题目 : LeetCode 191.位1的个数 191. 位1的个数…

Java入门篇 之 抽象类接口

本篇碎碎念&#xff1a;个人认为压力是一种前进的动力&#xff0c;但是不要有太多压力&#xff0c;不然会使心情烦躁&#xff0c;会控制不住自己的情绪&#xff0c;会在一个临界值爆发&#xff0c;一旦爆发&#xff0c;将迟迟不能消散 今日份励志文案: 努力的背后必有加倍的赏赐…

配置VNC环境时,出现xauth: file /root/.Xauthority does not exist的解决方案。

问题描述 在配置VNC&#xff08;Virtual Network Computing&#xff09;环境的过程时&#xff0c;首先安装了tigervnc-server包。在使用&#xff1a; vncserver命令创建VNC会话号的时候出现了一个报错&#xff1a;xauth: file /root/.Xauthority does not exist 原因分析&…

大数据HCIE成神之路之数学(3)——概率论

概率论 1.1 概率论内容介绍1.1.1 概率论介绍1.1.2 实验介绍 1.2 概率论内容实现1.2.1 均值实现1.2.2 方差实现1.2.3 标准差实现1.2.4 协方差实现1.2.5 相关系数1.2.6 二项分布实现1.2.7 泊松分布实现1.2.8 正态分布1.2.9 指数分布1.2.10 中心极限定理的验证 1.1 概率论内容介绍…

Sql Server 2017主从配置之:发布订阅

使用发布订阅模式搭建Sql Server 2017主从同步&#xff0c;类似事件通知机制&#xff0c;基本可以做到准实时同步&#xff0c;可以同时做到一对多的数据同步。 不过发布订阅模式&#xff0c;只能同时数据&#xff0c;不能同步表结构。在创建发布的时候&#xff0c;需要选择需要…

Hive调优

1.参数配置优化 设定Hive参数有三种方式&#xff1a; &#xff08;1&#xff09;配置Hive文件 当修改配置Hive文件的设定后&#xff0c;对本机启动的所有Hive进程都有效&#xff0c;因此配置是全局性的。 一般地&#xff0c;Hive的配置文件包括两部分&#xff1a; a&#xff…

使用Spring Boot结合JustAuth实现支付宝、微信、微博扫码登录功能

使用Spring Boot结合JustAuth实现支付宝、微信、微博扫码登录功能 在使用Spring Boot结合JustAuth实现支付宝、微信、微博扫码登录功能之前&#xff0c;需要先确保已经配置好Spring Boot项目&#xff0c;并且添加了JustAuth的依赖。你可以在项目的pom.xml文件中添加如下依赖&a…

spass-二元变量相关分析

基础概念 计算相关系数r&#xff1a;利用样本数据计算样本相关系数&#xff0c;样本相关系数反映了两变量间线性相关程度的强弱。相关系数的取值范围界于-1与1之间&#xff0c;即-1≤r≤1 当0<r ≤ 1&#xff0c;表明变量之间存在正相关关系&#xff1b; 当-1 ≤ r…

BatchNormalization:解决神经网络中的内部协变量偏移问题

ICML2015 截至目前51172引 论文链接 代码连接(planing) 文章提出的问题 减少神经网络隐藏层中的”内部协变量偏移”问题。 在机器学习领域存在“协变量偏移”问题,问题的前提是我们划分数据集的时候,训练集和测试集往往假设是独立同分布(i.i.d)的,这种独立同分布更有利于…

C++--哈希表--散列--冲突--哈希闭散列模拟实现

文章目录 哈希概念一、哈希表闭散列的模拟实现二、开散列(哈希桶)的模拟实现数据类型定义析构函数插入查找删除 哈希概念 unordered系列的关联式容器之所以效率比较高&#xff0c;是因为其底层使用了哈希结构。 顺序结构以及平衡树中&#xff0c;元素关键码与其存储位置之间没…

校园服装定制服务预约小程序的效果如何

对校园服装定制商家而言&#xff0c;如今线下流量稀缺&#xff0c;同行多且竞争激烈&#xff0c;同时这一行面对的消费者非大众&#xff0c;因此各品牌间都在通过各种方式进行同城或多地的客户拓展&#xff0c;但线下方式无疑是比较低效的。线上是一个不错的选择&#xff0c;不…

元素水平垂直居中

方法一&#xff1a;定位transform 方法二&#xff1a;flex布局 方法三&#xff1a;定位margin【需要child 元素自身的宽高】 相关HTML代码&#xff1a; <div class"parent"><div class"child"></div> </div> 方法一&#xff1a…

pythongui实时闹钟

# codinggbk import tkinter as tk from time import strftime# 创建一个主窗口 root tk.Tk() root.title("实时闹钟")# 设置窗口的大小不可变 root.resizable(False, False)# 设置窗口始终保持在最上层 root.attributes(-topmost, True)# 更新时间的函数 def time(…

力扣 字母异位词分组 哈表 集合

&#x1f468;‍&#x1f3eb; 力扣 字母异位词分组 ⭐ 思路 由于互为字母异位词的两个字符串包含的字母相同&#xff0c;因此对两个字符串分别进行排序之后得到的字符串一定是相同的&#xff0c;故可以将排序之后的字符串作为哈希表的键。 &#x1f351; AC code class Solut…

Docker中快速安装RabbitMQ

文章目录 前言一、安装Docker二、安装RabbitMQ无脑命令行运行 总结 前言 在Ubuntu中的Docker容器中快速安装RabbitMQ&#xff0c;亲测有效&#xff0c;不废话&#xff0c;上操作。 一、安装Docker 直接按照Docker官方教程操作&#xff1a;官方安装教程 点进官网&#xff0c;往…