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

简介: 行为型设计模式01-策略模式

策略模式

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



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;
    }
}

570f03886e6af1a87d3b534ee7a1e50e.png


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


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

4、策略模式


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


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


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


策略接口定义了所有具体策略类所需要实现的方法

具体策略类实现了策略接口,并提供了不同的算法实现

环境类则持有一个策略接口类型的引用,并将实际的算法执行委托给该引用所指向的具体策略类。

策略模式的优点


可以方便地扩展和修改算法的实现,而不必修改环境类的代码。

可以将算法的实现与其他部分的代码分离,提高代码的可维护性和可复用性。

对比简单工厂模式

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

策略模式结构图




3d383af9b7aa7aa5087c782cca13fac9.png



具体说明:

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


f6824701876ea862d43de62fd924a4c6.png


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


0bc6b8f144e4a3a4afcabdc52902379d.png


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


86a1da4047fea353b346d7ae86f421b3.png


客户端:



9abeb008a1ddec3f7af70534a0efb147.png

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

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

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 + "元");
}


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

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

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


7b293dd3ec68c18113ab2d8dcb980fe9.png



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

6、解析策略模式


6.1 策略模式总结

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

6.2 策略模式的优点


简化单元测试:通过对应的接口进行单独测试,就不用测试到其他的功能接口。、

算法可重用性强:将算法封装成独立的类,使其可以在不同的应用中被复用,提高了代码的可维护性和可扩展性。

策略切换方便:对于相同的行为,在不同的场景下可能需要不同的实现算法,策略模式可以方便地切换算法实现。

避免使用多重条件分支语句:使用策略模式可以避免使用复杂的if-else分支语句,提高代码的可读性和可维护性,降低代码的圈复杂度

扩展性良好:向系统中增加新的策略类很容易,不需要修改原有代码,符合开闭原则。

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


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


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

6.2 策略模式的优点

  1. 简化单元测试:通过对应应的接口进行单独测试,就不用测试到其他的功能接口。、

算法可重用性强:将算法封装成独立的类,使其可以在不同的应用中被复用,提高了代码的可维护性和可扩展性。

策略切换方便:对于相同的行为,在不同的场景下可能需要不同的实现算法,策略模式可以方便地切换算法实现。

避免使用多重条件分支语句:使用策略模式可以避免使用复杂的if-else分支语句,提高代码的可读性和可维护性,降低代码的圈复杂度

扩展性良好:向系统中增加新的策略类很容易,不需要修改原有代码,符合开闭原则。

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


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


目录
相关文章
|
2月前
|
设计模式 算法 测试技术
PHP中的设计模式:策略模式的应用与实践
在软件开发的浩瀚海洋中,设计模式如同灯塔,指引着开发者们避开重复造轮子的暗礁,驶向高效、可维护的代码彼岸。今天,我们将聚焦于PHP领域中的一种重要设计模式——策略模式,探讨其原理、应用及最佳实践,揭示如何通过策略模式赋予PHP应用灵活多变的业务逻辑处理能力,让代码之美在策略的变换中熠熠生辉。
|
5天前
|
设计模式 算法 Kotlin
Kotlin教程笔记(53) - 改良设计模式 - 策略模式
Kotlin教程笔记(53) - 改良设计模式 - 策略模式
23 2
|
26天前
|
设计模式 算法 Kotlin
Kotlin教程笔记(53) - 改良设计模式 - 策略模式
本教程详细讲解Kotlin语法,适合深入学习。快速入门可参考“简洁”系列教程。本文通过游泳运动员的案例,介绍策略模式及其在Kotlin中的改良应用,利用高阶函数简化代码结构,提高灵活性。
27 3
|
27天前
|
设计模式 算法 Kotlin
Kotlin教程笔记(53) - 改良设计模式 - 策略模式
本教程详细讲解Kotlin语法,适合深入学习。快速入门可参考“简洁”系列教程。本文介绍策略模式在Kotlin中的应用,通过游泳运动员的例子,展示如何使用接口和高阶函数实现策略模式,使代码更简洁、灵活。
27 2
|
28天前
|
设计模式 算法 Kotlin
Kotlin教程笔记(53) - 改良设计模式 - 策略模式
Kotlin教程笔记(53) - 改良设计模式 - 策略模式
59 3
|
29天前
|
设计模式 算法 Kotlin
Kotlin教程笔记(53) - 改良设计模式 - 策略模式
Kotlin教程笔记(53) - 改良设计模式 - 策略模式
27 3
|
29天前
|
设计模式 算法 PHP
PHP中的设计模式:策略模式的深入解析与实践
【10月更文挑战第9天】 策略模式是一种行为设计模式,它允许在运行时选择算法的行为。在PHP开发中,通过使用策略模式,我们可以轻松切换算法或逻辑处理方式而无需修改现有代码结构。本文将深入探讨策略模式的定义、结构以及如何在PHP中实现该模式,并通过实际案例展示其应用价值和优势。
28 1
|
29天前
|
设计模式 算法 PHP
PHP中的设计模式:策略模式的深入解析与应用
【10月更文挑战第8天】 在软件开发的浩瀚宇宙中,设计模式如同星辰指引,照亮了代码设计与架构的航道。本文旨在深入探索PHP语境下策略模式(Strategy Pattern)的精髓,不仅剖析其内核原理,还将其融入实战演练,让理论在实践中生根发芽。策略模式,作为解决“如何优雅地封装算法族”的答案,以其独特的灵活性与扩展性,赋予PHP应用以动态变换行为的能力,而无需牵动既有的类结构。
17 2
|
30天前
|
设计模式 算法 PHP
PHP中的设计模式:策略模式的深入解析与实践
在PHP开发中,设计模式是提高代码可读性、可维护性和扩展性的重要工具。本文将深入探讨策略模式这一行为型设计模式,通过分析其定义、结构、使用场景以及在PHP中的实际应用,帮助开发者更好地理解和运用策略模式来优化自己的项目。不同于传统摘要的简洁概述,本文摘要部分将详细阐述策略模式的核心理念和在PHP中的实现方法,为读者提供清晰的指引。
|
1月前
|
设计模式 算法 PHP
PHP中的设计模式:策略模式的深入解析与实践
策略模式是一种行为型设计模式,用于定义一系列算法,将每种算法都封装起来,并使它们可以互换。本文将探讨如何在PHP中实现策略模式,以及如何利用它来提高代码的灵活性和可维护性。通过具体示例,我们将看到策略模式在处理复杂业务逻辑时的优势,从而帮助开发者编写出更加清晰、易于扩展的代码。