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

news2024/11/20 20:29:20

✨作者:猫十二懿

❤️‍🔥账号:CSDN 、掘金 、个人博客 、Github

🎉公众号:猫十二懿

策略模式

问题引入:实现一个商场收银软件,简单的实现就是单价和数量的乘积。

1、商场收银软件

下面就来看看这个实现程序:

/**
 * @author Shier
 * CreateTime 2023/4/10 21:40
 */
public class ShopCash {
    public static void main(String[] args) {
        //商品单价
        double price = 0d;
        //商品购买数量
        int num = 0;
        //当前商品合计费用
        double totalPrices = 0d;
        //总计所有商品费用
        double total = 0d;
        Scanner sc = new Scanner(System.in);
        // 不断输入,知道输入的价格或和数量小于0 
        do {
            System.out.print("请输入商品单价:");
            price = Double.parseDouble(sc.nextLine());
            System.out.print("请输入商品数量:");
            num = Integer.parseInt(sc.nextLine());
            if (price > 0 && num > 0) {
                totalPrices = price * num;
                total = total + totalPrices;
                System.out.println("单价:" + price + "元  数量:" + num + " 合计:" + totalPrices + "元");
                System.out.println("总计:" + total + "元");
            }
        }
        while (price > 0 && num > 0);
    }
}

以上的简单实现的存在着一定的问题的,比如如果商场举办活动,所有的商品打八折,又该如何实现。

起始很简单:只要(total + totalPrices)*0.8 即可,但是这样真的好吗。如果商场活动结束了呢,又得来改程序,然后再重新安装软件,这样虽不说很麻烦啊,而且现实生活中也不会的。

2、增加打折变量

将以上的程序修改如下:

/**
 * @author Shier
 * CreateTime 2023/4/10 21:40
 */
public class ShopCashDemo02 {
    public static void main(String[] args) {
        //商品单价
        double price = 0d;
        //商品购买数量
        int num = 0;
        //当前商品合计费用
        double totalPrices = 0d;
        // 打几折
        int discount = 0;
        //总计所有商品费用
        double total = 0d;
        Scanner sc = new Scanner(System.in);
        // 不断输入,知道输入的价格或和数量小于0
        do {
            System.out.println("请输入商品折扣模式(1.正常收费 2.打八折 3.打七折):");
            discount = Integer.parseInt(sc.nextLine());
            System.out.print("请输入商品单价:");
            price = Double.parseDouble(sc.nextLine());
            System.out.print("请输入商品数量:");
            num = Integer.parseInt(sc.nextLine());
            if (price > 0 && num > 0) {
                // 判断打折
                switch (discount) {
                    case 1:
                        totalPrices = price * num;
                        break;
                    case 2:
                        totalPrices = price * num * 0.8;
                        break;
                    case 3:
                        totalPrices = price * num * 0.7;
                        break;
                    default:
                        break;
                }
            }
            totalPrices = price * num;
            total = total + totalPrices;
            System.out.println("单价:" + price + "元  数量:" + num + " 合计:" + totalPrices + "元");
            System.out.println("总计:" + total + "元");
        }
        while (price > 0 && num > 0);
    }
}

通过一个变量控制打几折,来进行计算总的价格。这样子比说明的第一个简单的实现看起来灵活了很多。

但是你观察发现,是不是除了打几折以外,其他的代码基本都一样的。如果再增加多几个需求(比如满100饭20等活动),哪又该如何实现?

可以先写一个父类,再继承它实现多个打折和返利的子类,利用多态,完成这个代码。

3、简单工厂实现

这里打折基本都是一样的,只要有个初始化参数就可以了。满几送几的,需要两个参数才行。

面向对象的编程,并不是类越多越好,类的划分是为了封装,但分类的基础是抽象,具有相同属性和功能的对象的抽象集合才是类。

打一折和打九折只是形式的不同,抽象分析出来,所有的打折算法都是一样的,所以打折算法应该是一个类。

具体的代码实现如下:

/**
 * @author Shier 
 * CreateTime 2023/4/10 21:51
 * 抽象类
 */
public abstract class CashSuper {
    public abstract double acceptCash(double price,int num);
}
/**
 * @author Shier
 * CreateTime 2023/4/10 21:54
 *
 * 正常收费,原价返回
 */
public class CashNormal extends CashSuper {
    public double acceptCash(double price,int num){
        return price * num;
    }
}
/**
 * @author Shier
 * CreateTime 2023/4/10 21:53
 *
 * 打折收费
 */
public class CashRebate extends CashSuper {
    private double moneyRebate = 1d;
    //初始化时必需输入折扣率。八折就输入0.8
    public CashRebate(double moneyRebate){
        this.moneyRebate = moneyRebate;
    }

    //计算收费时需要在原价基础上乘以折扣率
    public double acceptCash(double price,int num){
        return price * num * this.moneyRebate;
    }
}
/**
 * @author Shier
 * CreateTime 2023/4/10 21:55
 *
 * 返利
 */
public class CashReturn extends CashSuper {
    //返利条件
    private double moneyCondition = 0d;
    //返利值
    private double moneyReturn = 0d;

    //返利收费。初始化时需要输入返利条件和返利值。
    //比如“满300返100”,就是moneyCondition=300,moneyReturn=100
    public CashReturn(double moneyCondition,double moneyReturn){
        this.moneyCondition = moneyCondition;
        this.moneyReturn = moneyReturn;
    }
    //计算收费时,当达到返利条件,就原价减去返利值
    public double acceptCash(double price,int num){
        double result = price * num;
        if (moneyCondition>0 && result >= moneyCondition)
           // 比如 result = 420 - (420/300) * 100 = 320 320就是优惠之后的价格,这个情况是满300减一百,满六百见两百了
            //result = result - Math.floor(result / moneyCondition) * moneyReturn;
            // 只要大于300,不管多大,都只有100返利
            result = result - moneyReturn;
        return result;
    }
}
/**
 * @author Shier
 * CreateTime 2023/4/10 21:50
 * 收费工厂
 */
public class CashFactory {
    public static CashSuper createCashAccept(int cashType){
        CashSuper cs = null;
        switch (cashType) {
            case 1:
                //正常收费
                cs = new CashNormal();
                break;
            case 2:
                //打八折
                cs = new CashRebate(0.8d);
                break;
            case 3:
                //打七折
                cs = new CashRebate(0.7d);
                break;
            case 4:
                //满300返100
                cs = new CashReturn(300d,100d);
                break;
            default:
                break;
        }
        return cs;
    }
}

以上类与类之间的关系如下:

image-20230410220201689

还有其他的需求,只要在收费对象工厂添加对应的条件即可。

简单工厂模式虽然也能解决这个问题,但这个模式只是解决对象的创建问题,而且由于工厂本身包括所有的收费方式,商场是可能经常性地更改打折额度和返利额度,每次维护或扩展收费方式都要改动这个工厂,以致代码需重新编译部署,这真的是很糟糕的处理方式,所以用它不是最好的办法。面对算法的时常变动,应该有更好的办法。

4、策略模式

策略模式(Strategy Pattern)是一种面向对象设计模式,它在一个对象中封装了不同的算法,使得这些算法可以相互替换。通过使用策略模式,客户端可以选择不同的算法来完成特定的任务,同时还可以轻松地替换算法和添加新的算法。

策略模式(Strategy):它定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式当算法的变化时,不会影响到使用算法的客户。

在策略模式中,一般有三个角色:策略接口、具体策略类和环境类。

  1. 策略接口定义了所有具体策略类所需要实现的方法
  2. 具体策略类实现了策略接口,并提供了不同的算法实现
  3. 环境类则持有一个策略接口类型的引用,并将实际的算法执行委托给该引用所指向的具体策略类。

策略模式的优点

  1. 可以方便地扩展和修改算法的实现,而不必修改环境类的代码。
  2. 可以将算法的实现与其他部分的代码分离,提高代码的可维护性和可复用性。

对比简单工厂模式

商场收银时如何促销,用打折还是返利,其实都是一些算法,用工厂来生成算法对象,这没有错,但算法本身只是一种策略,最重要的是这些算法是随时都可能互相替换的,这就是变化点,而封装变化点是我们面向对象的一种很重要的思维方式。

策略模式结构图

image-20230410224824707

具体说明:

Strategy类,定义所有支持的算法的公共接口:

image-20230410225054931

ConcreteStrategy类,封装了具体的算法或行为,继承于Strategy:

image-20230410225125003

Context类,用一个ConcreteStrategy来配置,维护一个对Strategy对象的引用:

image-20230410225147981

客户端:

image-20230410225201059

5、策略模式实现

观察发现,我们只要修改客户端,并且曾加一个Context类即可,下面的商场收银系统的结构图

image-20230411225417536

将上面的简单工厂模式进行修改:

CashContext

/**
 * @author Shier
 * CreateTime 2023/4/11 22:55
 * CashContext类
 */
public class CashContext {
    /**
     * CashSuper对象
     */
    private CashSuper cashSuper;

    /**
     * 通过构造方法,传入具体的收费策略
     */
    public CashContext(CashSuper cashSuper) {
        this.cashSuper = cashSuper;
    }

    /**
     * 根据不同的收费策略返回不同的结构
     */
    public double getResult(double price, int num) {
        return cashSuper.acceptCash(price, num);
    }
}

客户端修改如下:

/**
 * @author Shier
 * CreateTime 2023/4/10 21:40
 */
public class ShopCashDemo04 {
    public static void main(String[] args) {
        //商品单价
        double price = 0d;
        //商品购买数量
        int num = 0;
        //当前商品合计费用
        double totalPrices = 0d;
        // 打几折
        int discount = 0;
        //总计所有商品费用
        double total = 0d;

        Scanner sc = new Scanner(System.in);
        // 不断输入,知道输入的价格或和数量小于0
        do {
            System.out.print("请输入商品折扣模式(1.正常收费 2.打八折 3.打七折 4.满300返100):");
            discount = Integer.parseInt(sc.nextLine());
            System.out.print("请输入商品单价:");
            price = Double.parseDouble(sc.nextLine());
            System.out.print("请输入商品数量:");
            num = Integer.parseInt(sc.nextLine());
            if (price > 0 && num > 0) {
                // Context
                CashContext cashContext = null;
                //根据用户输入,将对应的策略对象作为参数传入CashContent对象中
                switch (discount) {
                    case 1:
                        cashContext = new CashContext(new CashNormal());
                        break;
                    case 2:
                        cashContext = new CashContext(new CashRebate(0.8d));
                        break;
                    case 3:
                        cashContext = new CashContext(new CashRebate(0.7d));
                        break;
                    case 4:
                        cashContext = new CashContext(new CashReturn(300d, 100d));
                        break;
                    default:
                        break;
                }
                //通过Context的getResult方法的调用,可以得到收取费用的结果
                //让具体算法与客户进行了隔离
                totalPrices = cashContext.getResult(price, num);
                total = total + totalPrices;
                System.out.println("单价:" + price + "元  数量:" + num + " 合计:" + totalPrices + "元");
                System.out.println("总计:" + total + "元");
            }
        }
        while (price > 0 && num > 0);
    }
}

但是你发现又多了很多的switch判断,这样又回到了最初的样子。下面再次将客户端的代码迁移到CashContext中

CashContext

/**
 * 通过构造方法,传入具体的收费策略
 */
public CashContext(int cashType) {
    //根据用户输入,将对应的策略对象作为参数传入CashContent对象中
    switch (cashType) {
        case 1:
            cashSuper = new CashNormal();
            break;
        case 2:
            cashSuper = new CashRebate(0.8d);
            break;
        case 3:
            cashSuper = new CashRebate(0.7d);
            break;
        case 4:
            cashSuper = new CashReturn(300d, 100d);
            break;
        default:
            break;
    }
}

在对CashContext进行调用时,就让他传入对应的折扣模式,然后则去创建对应的具体策略。

客户端修改:

if (price > 0 && num > 0) {
    // 根据用户输入的折扣模式,调用不同CashContext中对应的对象
    CashContext cashContext = new CashContext(discount);
    // 将计算放在各个具体策略类当中去完成
    totalPrices = cashContext.getResult(price, num);
    total = total + totalPrices;
    System.out.println("单价:" + price + "元  数量:" + num + " 合计:" + totalPrices + "元");
    System.out.println("总计:" + total + "元");
}

策略模式与简单工厂模式对比

对比项策略模式简单工厂模式
定义定义一系列算法,使算法的使用与算法分离开来,封装的算法具有一定独立性一个工厂类根据传入的参数动态决定创建哪种产品类的实例
解决问题解决在多重条件分支语句下的代码臃肿和难以维护的问题解决对象的创建问题,通过工厂类统一创建对象,避免客户端直接调用产品类
应用场景处理不同的业务场景,如支付策略、商品促销等对象的创建需要一定的复杂度,使用简单工厂可以减少客户端代码的复杂度
类别行为型设计模式创建型设计模式
耦合度
优点可以动态切换算法,易于扩展和添加新的算法实现适用于大量的产品创建,客户端只需要知道产品的类型即可
缺点增加类的数量,提高了系统的复杂度工厂类职责过重,违反了单一职责原则

下图说明两个模式之间的耦合度,关联越多的类,耦合度越高,反之月底。

image-20230412171918857

在上面的商场收银系统中,客户端实例化的是CashContext的对象,调用的是CashContext的方法GetResult,这使得具体的收费算法彻底地与客户端分离。连算法的父类CashSuper都不让客户端认识了。

6、解析策略模式

6.1 策略模式总结

策略模式是一种定义一系列算法的方法,从概念上来看,所有这些算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合

6.2 策略模式的优点

  1. 简化单元测试:通过对应的接口进行单独测试,就不用测试到其他的功能接口。、
  2. 算法可重用性强:将算法封装成独立的类,使其可以在不同的应用中被复用,提高了代码的可维护性和可扩展性。
  3. 策略切换方便:对于相同的行为,在不同的场景下可能需要不同的实现算法,策略模式可以方便地切换算法实现。
  4. 避免使用多重条件分支语句:使用策略模式可以避免使用复杂的if-else分支语句,提高代码的可读性和可维护性,降低代码的圈复杂度
  5. 扩展性良好:向系统中增加新的策略类很容易,不需要修改原有代码,符合开闭原则。

但是上面的程序还是不够完美的,因为每次都要去修改CashContext中的switch代码(有需求就修改,但是需求的变更是要成本,开通VIP帮你一步到位😎)

PS:使用抽象工厂模式:反射机制

模式是一种定义一系列算法的方法,从概念上来看,所有这些算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用所有的算法,减少了各种算法类与使用算法类之间的耦合**

6.2 策略模式的优点

  1. 简化单元测试:通过对应的接口进行单独测试,就不用测试到其他的功能接口。、
  2. 算法可重用性强:将算法封装成独立的类,使其可以在不同的应用中被复用,提高了代码的可维护性和可扩展性。
  3. 策略切换方便:对于相同的行为,在不同的场景下可能需要不同的实现算法,策略模式可以方便地切换算法实现。
  4. 避免使用多重条件分支语句:使用策略模式可以避免使用复杂的if-else分支语句,提高代码的可读性和可维护性,降低代码的圈复杂度
  5. 扩展性良好:向系统中增加新的策略类很容易,不需要修改原有代码,符合开闭原则。

但是上面的程序还是不够完美的,因为每次都要去修改CashContext中的switch代码(有需求就修改,但是需求的变更是要成本,开通VIP帮你一步到位😎)

PS:使用抽象工厂模式:反射机制

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

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

相关文章

掌握RDD分区

零、本讲学习目标 学会如何指定分区数量会定义与使用自定义分区器 一、RRD分区 (一)RDD分区概念 RDD是一个大的数据集合,该集合被划分成多个子集合分布到了不同的节点上,而每一个子集合就称为分区(Partition&#…

3.5 RDD持久化机制

一、RDD持久化 1、不采用持久化操作 查看要操作的HDFS文件 以集群模式启动Spark Shell 按照图示进行操作,得RDD4和RDD5 查看RDD4内容,会从RDD1到RDD2到RDD3到RDD4跑一趟 显示RDD5内容,也会从RDD1到RDD2到RDD3到RDD5跑一趟 2、采用持久化…

python解析html数据,获取到的链接是以/或 ./ 或 ../ 开头的相对链接,不是以http开头的,需要补全

一、实现的目标 在使用爬虫获取网页html数据时,解析到的链接是/或./ 开头的相对链接,不是以http开头的链接,如:/picture/0/cca65350643c441e80d390ded3975db0.png 。此时需要完成对该链接的补全,以得到正确的链接。此外,我们需要将解析到的html数据保存到起来,将来需要展…

3.8 Spark RDD典型案例

一、利用RDD计算总分与平均分 (一)准备工作 1、启动HDFS服务 2、启动Spark服务 3、在本地创建成绩文件 4、将成绩文件上传到HDFS (二)完成任务 1、在Spark Shell里完成任务 (1)读取成绩文件&#xff…

【搭建轻量级图床】本地搭建LightPicture开源图床管理系统,并公网远程访问

文章目录 1.前言2. Lightpicture网站搭建2.1. Lightpicture下载和安装2.2. Lightpicture网页测试2.3.cpolar的安装和注册 3.本地网页发布3.1.Cpolar云端设置3.2.Cpolar本地设置 4.公网访问测试5.结语 1.前言 现在的手机越来越先进,功能也越来越多,而手机…

二十三种设计模式第九篇--代理模式

在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。 意图:为其他对象提供一种代理以控制对这…

示范性微电子院校“抢人”,芯片赛道黄不了!

经常看到有同学问,“国内高校微电子专业最好的是哪所高校?”“想搞数字ic设计去哪所大学好呢?” 其实国内28所示范性微电子学院都是非常不错的选择。 2015年,九所示范性微电子院校名单公布,包括了清华大学、北京大学、复旦大学…

8、Linux C/C++ 实现MySQL的图片插入以及图片的读取

本文结合了Linux C/C 实现MySQL的图片插入以及图片的读取,特别是数据库读写的具体流程 一、文件读取相关函数 fseek() 可以将文件指针移动到文件中的任意位置。其基本形式如下: int fseek(FILE *stream, long offset, int whence);其中,str…

kafka 设置用户密码和通过SpringBoot测试

叙述 当前Kafka认证方式采用动态增加用户协议。 自0.9.0.0版本开始Kafka社区添加了许多功能用于提高Kafka群集的安全性,Kafka提供SSL或者SASL两种安全策略。SSL方式主要是通过CA令牌实现,此文主要介绍SASL方式。 1)SASL验证: 验证方式Kaf…

【JavaSE】Java基础语法(六):方法详解

文章目录 1. 方法概述1.1 方法的概念 2. 方法的定义和调用2.1 方法的定义2.2 方法的调用过程 3. 带参数方法的定义和调用3.1 带参数方法定义和调用3.2 形参和实参 4. 带返回值方法的定义和调用4.1 带返回值方法定义和调用4.2 带返回值方法的练习-求两个数的最大值(应用) 5. 方法…

【链接】深入理解PLT表和GOT表

系列综述: 💞目的:本系列是个人整理为了秋招面试的,整理期间苛求每个知识点,平衡理解简易度与深入程度。 🥰来源:材料主要源于多处理器编程的艺术进行的,每个知识点的修正和深入主要…

nest日志包pino、winston配置-懒人的折腾

nest日志 三种node服务端日志选型 winstonpinolog4js 2023年5月23日 看star数:winston > pino > log4js 使用体验: pino 格式简洁,速度快,支持输入日志到任意数据库,日志暂无自动清理(可能是我…

AI是怎么帮我写代码,写SQL的?(本文不卖课)

近期,ChatGPT风起云涌,“再不入局,就要被时代淘汰”的言论甚嚣尘上,借着这一波创业的朋友都不止3-4个,如果没记错,前几次抛出该言论的风口似乎是区块链,元宇宙,WEB3.0。 画外音&…

动态规划问题实验:数塔问题

目录 前言实验内容实验流程实验过程实验分析伪代码代码实现分析算法复杂度用例测试 总结 前言 动态规划是一种解决复杂问题的方法,它将一个问题分解为若干个子问题,然后从最简单的子问题开始求解,逐步推导出更复杂的子问题的解,最…

绝世内功秘籍《调试技巧》

本文作者:大家好,我是paper jie,感谢你阅读本文,欢迎一建三连哦。 内容专栏:这里是《C知识系统分享》专栏,笔者用重金(时间和精力)打造,基础知识一网打尽,希望可以帮到读者们哦。 内…

CloudQuery v2.0.0 发布 新增数据保护、数据变更、连接管理等功能

哈喽社区的小伙伴们,经过一个月的努力,CloudQuery 社区版发布了全新 v2.0.0系列! 对比 v1.5.0,v2.0.0 在整体 UI 界面上就做了很大调整,功能排布我们做了重新梳理,可以说,社区版 v2.0.0 带领 C…

Linux——makefile自动化构建工具

一. 前言 一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的 规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂 的功能…

数据结构的定义

主要的定义 数据 描述客观事物的数和字符的集合,比如文字,数字和特殊符号 基本单元:数据元素 一个数据单元由若干个数据项构成 数据项:具有独立含义的数据最小单元,也称字段或域 数据元素&…

Spring Boot 中的 Starter 是什么?如何创建自定义 Starter?

Spring Boot 中的 Starter 是什么?如何创建自定义 Starter? Spring Boot 是一个快速构建应用程序的框架,它提供了一种简单的方式来快速启动和配置 Spring 应用程序。Spring Boot Starter 是 Spring Boot 的一个重要概念,它可以帮…

计算机网络详细笔记(四)网际控制报文协议ICMP

文章目录 4.网际控制报文协议ICMP4.1.ICMP报文的种类4.2.ICMP应用举例 4.网际控制报文协议ICMP 网际控制报文协议概述:: 作用:更有效地转发IP数据报和提高交付成功的机会。原理:允许主机或路由器报告差错情况和提供有关异常情况…