策略模式——时势造影响

news2024/11/25 10:41:25

● 策略模式介绍

        在软件开发中常常遇到这样的情况:实现某一个功能可以有多种算法或者策略,我们根据实际情况选择不同的算法或者策略来完成该功能。例如,排序算法,可以使用插入排序、归并排序、冒泡排序。

        针对这种情况,一种常规的方法时将多种算法写在一个类中。例如,需要提供多种排序算法,可以将这些算法写在一个类中,每一个方法对应具体的排序算法;当然,也可以将这些排序算法封装在一个统一的方法中,通过if...else...或者case等条件判断语句来选择具体的算法。这两种实现方式我们都可以称为硬编码。然而,当很多个算法集中在一个类中时,这个类就会变的很臃肿,这个类的维护成本会变高,在维护时更容易引发错误。如果我们需要增加一种新的算法,需要修改封装算法类的源代码。这就明显违反了OCP原则和单一职责原则。

        如果将这些算法或者策略抽象出来,提供一个统一的接口,不同的算法或者策略有不同的实现类,这样在程序客户端就可以通过注入不同的实现对象来实现算法或者策略的动态替换,这种模式的可扩展性、可维护性也就更高,也就是我们要说的策略模式。

● 策略模式的定义

        策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以互相替换。策略模式让算法独立于使用它的客户端而独立变化。

● 策略模式的使用场景

        (1)针对同一类型问题的多种处理方式,仅仅是具体行为有差别时。

        (2)需要安全地封装多种同一类型的操作时。

        (3)出现同一抽象类有多个子类,而又需要使用if-else或者swifch-case来选择具体子类时。

● 策略模式的UML类图

        UML类图如下图所示:

        

         上图中的角色介绍:

        (1)Content——用来操作策略的上下文环境;

        (2)Stragety——策略的抽象;

        (3)ConcreteAtragetyA、ConcreteStragetyB——具体的策略实现。

● 策略模式的简单实现

        通常如果一个问题有多个解决方案时,最简单的方式就是利用if-else或者switch-case方法根据不同的情景选择不同的解决方案,但这种简单的方案问题太多,例如耦合性太高、代码臃肿、难以维护等。但是,如果解决方案中包含大量的处理逻辑需要封装,或者处理方式变动较大的时候则就显得混乱、复杂,当需要增加一种方案时就需要修改类中的代码。怎么对于修改不关闭?不是说好要遵循开闭原则吗?

        是的,if-else这种方法确实不会遵循开闭原则,而应对这种情况策略模式就能很好地解决这类问题,它将各种方案分离开来,让程序客户端根据具体的需求来动态地选择不同的策略方法。

        下面我们以坐公共交通工具的费用计算来演示一个简单的示例。有一种公交计费方式不再是单一票价制,而是分段计价,这就是说乘坐的距离越远,价格越高。

        虽然,公交车和地铁的价格计算方式是不一样的,但是,我们的示例中需要乘不用的出行工具的成本,下面是我们的第一个版本的代码。

public class PriceCalculator {
    //公交车类型
    private static final int BUS = 1;
    //地铁类型
    private static final int SUBWAY = 2;

    public static void main(String[] args) {
        PriceCalculator priceCalculator = new PriceCalculator();
        System.out.println("坐16公里的公交车票价为 : " + priceCalculator.calculatePrice(16, BUS));
        System.out.println("坐16公里的地铁票价为 : " + priceCalculator.calculatePrice(16, SUBWAY));
    }

    /**
     * 公交车,十公里之内一元钱,超过十公里之后每加一元钱钱可以乘坐五公里
     *
     * @param km 公里
     * @return
     */
    private int busPrice(int km) {
        //超过十公里的总距离
        int extraTotal = km - 10;
        //超过的距离是五公里的倍数
        int extraFactor = extraTotal / 5;
        //超过的距离对五公里取余
        int fraction = extraTotal % 5;
        //价格计算
        int prince = 1 + extraFactor * 1;
        return fraction > 0 ? ++prince : prince;
    }

    /**
     * 6 公里(含)内3元;6~12公里(含)4元;12~22公里(含)5元;22~32公里(含)6元
     *
     * @param km
     * @return
     */
    private int subwayPrice(int km) {
        if (km <= 6) {
            return 3;
        } else if (km > 6 && km < 12) {
            return 4;
        } else if (km > 12 && km < 22) {
            return 5;
        } else if (km > 22 && km < 32) {
            return 6;
        }
        //其他距离我们简化为7元
        return 7;
    }

    int calculatePrice(int km, int type) {
        if (type == BUS) {
            return busPrice(km);
        } else if (type == SUBWAY) {
            return subwayPrice(km);
        }
        return 0;
    }
}

        PriceCalculator类很明显的问题就是并不是单一职责,首先它承担了计算公交车和地铁乘坐价格的职责;另一个问题就是通过if-else的形式来判断使用哪种计算形式。当我们增加一种出行方式时,如出租车,那么我们就需要在 PriceCalculator中增加一个方法来计算出租车出行的价格,并且在calculatePrice(int km , int type)函数中增加一个判断,代码添加后大致如下。

    //公交车类型
    private static final int BUS = 1;
    //地铁类型
    private static final int SUBWAY = 2;
    //出租车类型
    private static final int TAXI = 3;

     /**
     * 简单计算为每公里2元钱
     *
     * @param km
     * @return
     */
    private int taxiPrice(int km) {
        return km * 2;
    }

    int calculatePrice(int km, int type) {
        if (type == BUS) {
            return busPrice(km);
        } else if (type == SUBWAY) {
            return subwayPrice(km);
        } else if (type == TAXI) {
            return taxiPrice(km);
        }
        return 0;
    }

        此时的代码已经比较混乱,各种if-else语句缠绕其中。当价格的计算方式变化时,需要直接修改这个类中的代码,那么很可能有一段代码是其他几个计算方法所共同使用的,这就容易引入错误。另外,在增加出行方式时,我们有需要在calclatePrice中添加if-else,此时很有可能就是复制上一个if-else,然后手动修改,手动复制代码也是最容易引入错误的做饭之一。这类代码必然是难以应对变化的,他会使得代码变得越来越臃肿,难以维护,我们解决这类问题的手法也就是策略模式。当然,我们也可以把每种计算方式独立成一个函数,让后外部调用对应的方法即可,但这也是另一种耦合的形式,对于可变性较大的算法族来说还是不合适使用这种方式。

        下面我们对上诉示例用策略模式进行重构。

        首先我们需要定义一个抽象的价格计算接口,这里命名为CalculateStrategy,具体代码如下。

/**
 * 计算接口
 */
public interface CalculateStrategy {
    /**
     * 按距离来计算价格
     *
     * @param km 公里
     * @return 返回价格
     */
    int calculatePrice(int km);
}

        对于每一种出行方法我们都有一个独立的计算策略类,这些策略类都实现了CalculateStrategy接口,例如下面是公交车和地铁的计算策略类。

/**
 * 公交车价格计算类
 */
public class BusStrategy implements CalculateStrategy {
    /**
     * 公交车,十公里之内一元钱,超过十公里之后每加一元钱可以乘坐五公里
     *
     * @param km 公里
     * @return
     */
    @Override
    public int calculatePrice(int km) {
        //超过十公里的总距离
        int extraTotal = km - 10;
        //超过的距离是五公里的倍数
        int extraFactor = extraTotal / 5;
        //超过的距离对五公里取余
        int fraction = extraTotal % 5;
        //价格计算
        int prince = 1 + extraFactor * 1;
        return fraction > 0 ? ++prince : prince;
    }
}
/**
 * 地铁价格计算策略
 */
public class SubwayStrategy implements CalculateStrategy {
    /**
     * 6 公里(含)内3元;6~12公里(含)4元;12~22公里(含)5元;22~32公里(含)6元
     *
     * @param km 公里
     * @return
     */
    @Override
    public int calculatePrice(int km) {
        if (km <= 6) {
            return 3;
        } else if (km > 6 && km < 12) {
            return 4;
        } else if (km > 12 && km < 22) {
            return 5;
        } else if (km > 22 && km < 32) {
            return 6;
        }
        //其他距离我们简化为7元
        return 7;
    }
}

        我们在创建一个扮演Context角色的类,这里将它命名为TrafficCalculator,具体代码如下。

/**
 * 公交出行价格计算器
 */
public class TrafficCalculator {
    private CalculateStrategy mStrategy;

    public static void main(String[] args) {
        TrafficCalculator calculator = new TrafficCalculator();
        //设置计算策略
        calculator.setStrategy(new BusStrategy());
        //计算价格
        System.out.println("坐16公里的公交车票价为 : " + calculator.calculatePrice(16));
    }

    public void setStrategy(CalculateStrategy mStrategy) {
        this.mStrategy = mStrategy;
    }

    public int calculatePrice(int km) {
        return mStrategy.calculatePrice(km);
    }
}

        经过上诉的重构之后,去掉了各种各样的if-else语句,机构变得也很清晰,其结构图如下:

         这种方案在隐藏实现的同时,可扩展性变的很强,例如,当我们需要增加出租车的计算策略时,只需要添加一个出租车计算策略类,然后该策略设置给Traffic

Calcilator,最好直接通过TrafficCalcilator对象的计算方法即可。示例代码如下。

/**
 * 公交出行价格计算器
 */
public class TrafficCalculator {
    private CalculateStrategy mStrategy;

    public static void main(String[] args) {
        TrafficCalculator calculator = new TrafficCalculator();
        //设置计算策略
        calculator.setStrategy(new TaxiStrategy());
        //计算价格
        System.out.println("出租车乘坐16公里的价格 : " + calculator.calculatePrice(16));
    }

    public void setStrategy(CalculateStrategy mStrategy) {
        this.mStrategy = mStrategy;
    }

    public int calculatePrice(int km) {
        return mStrategy.calculatePrice(km);
    }
}

        通过上诉示例我们就可以清晰地看出二者的区别所在。前者通过if-else来解决问题,虽然实现较为简单,类型层级单一,但暴露的问题非常明显,即代码臃肿,逻辑复杂,难以升级和维护,没有结构可言;后者则是通过建立抽象,将不同的策略构建成一个具体的策略实现,通过不同的策略实现算法替换。在简化逻辑、结构的同时,增强了系统的可读性、稳定性、可扩展性,这对于较为复杂的业务逻辑显得更为直观,扩展也更为方便。

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

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

相关文章

机器学习 day09(如何设置学习率α,特征工程,多项式回归)

常见的错误的学习曲线图&#xff08;上方两个&#xff09; 当关于迭代次数的学习曲线图&#xff0c;出现波浪型或向上递增型&#xff0c;表示梯度下降算法出错该情况可由&#xff0c;学习率α过大&#xff0c;或代码有bug导致 常用的调试方法&#xff1a; 选择一个非常非常…

【学习笔记】unity脚本学习(六)【GUI发展历程、IMGUI控件、Layout自动布局】

目录 unity 界面发展IMGUINGUI其他GUI插件uGUIUI 工具包比较 GUI基础GUI静态变量Unity扩展编辑器屏幕空间的总尺寸Screen.width 和 Screen.height GUI静态函数&#xff08;GUI控件&#xff09;Label图片 Box控件Button与RepeatButtonTextFieldTextAreaPasswordField其他控件 GU…

MySql主从复制原理及部署

MySql主从复制 原理&#xff1a; 1、Master节点开启binlog&#xff0c;并将变动记录到binlog中&#xff1b; 2、Slave节点定期探测Master节点的binlog&#xff0c;如有变动&#xff0c;开启I/O线程向Master节点请求二进制事件&#xff1b; 3、Master节点为每一个I/O线程启动…

win10卸载MySQL8.0

停止MySQL服务 shiftctrlesc打开任务管理器 将MySQL服务停止&#xff0c;这里我只有一个MySQL服务&#xff0c;如有多个MySQL服务&#xff0c;也要全部停止掉。 卸载mysql server等设备 控制面板 -》程序 -》 程序和功能&#xff0c;将mysql server等设备卸载掉&#xff0c;好…

SpringCloudAlibaba服务熔断、限流——Sentinel

Sentinel 本专栏学习内容来自尚硅谷周阳老师的视频 有兴趣的小伙伴可以点击视频地址观看 简介 Sentinel是Alibaba公司推出的一个熔断与限流工具&#xff0c;相当于我们之前学习的Hystrix&#xff0c;可以解决服务使用中的各种问题&#xff0c;例如&#xff1a;服务雪崩、服务降…

minigpt4搭建过程记录,简单体验图文识别乐趣

引言 从3月开始&#xff0c;aigc进入了疯狂的开端&#xff0c;正如4月12日无界 AI直播 在《探索 AIGC 与人类合作的无限可能》中关于梳理的时间线一样&#xff0c;aigc的各种产品如雨后春笋般进入了不可逆的态势&#xff0c;里面有句话很形象&#xff0c;人间一日&#xff0c;…

信息收集(四)服务器信息收集

信息收集&#xff08;一&#xff09;域名信息收集 信息收集&#xff08;二&#xff09;IP信息收集 信息收集&#xff08;三&#xff09;端口和目录信息收集 WAF指纹识别 什么是WAF WAF的全称是&#xff08;Web Application Firewall &#xff09;Web 应用防火墙用来过滤HTTP…

最新国内免费chatgpt 的试用方法

方式一&#xff1a; 免费账号&#xff1a; 地址&#xff1a;gpt-easy.com 账号test666, 666666 方式二&#xff1a; wheart.cn 每3小时15次调用 方式三&#xff1a; Microsoft Edge 插件&#xff0c;每天30次免费 方式四&#xff1a; wetab插件&#xff0c;多源切换&am…

StarRocks 3.0 集群安装手册

本文介绍如何以二进制安装包方式手动部署最新版 StarRocks 3.0集群。 什么是 StarRocks StarRocks 是新一代极速全场景 MPP (Massively Parallel Processing) 数据库。StarRocks 的愿景是能够让用户的数据分析变得更加简单和敏捷。用户无需经过复杂的预处理&#xff0c;就可以…

2023年五月份图形化三级打卡试题

活动时间 从2023年5月1日至5月21日&#xff0c;每天一道编程题。 本次打卡的规则如下&#xff1a; 小朋友每天利用10~15分钟做一道编程题&#xff0c;遇到问题就来群内讨论&#xff0c;我来给大家答疑。 小朋友做完题目后&#xff0c;截图到朋友圈打卡并把打卡的截图发到活动群…

Android硬件通信之 GPIO通信

一&#xff0c;为什么要和硬件通信 1.1&#xff0c;做软件开发的可能大多只是在手机上做服务器/客户端这种应用&#xff0c;说白了这些只是对数据的处理&#xff0c;对数据做存储和读取&#xff0c;以及分析的工作。 1.2 但随着智能领域的发展&#xff0c;人们已不满足手动去…

医院核心数据库一体化建设实践

建设背景 “以数据为核心资源的数字化时代&#xff0c;正在成为引领和推动新一轮科技革命的核心力量&#xff0c;将会深刻影响卫生健康行业。” 这是四月份发布的《公立医院运营管理信息化功能指引》中对数据重要性的描述。数据库作为数据的载体&#xff0c;支撑着整个业务系…

李清照最经典的10首诗词

在三千年的诗歌艺术中&#xff0c;男人一直是绝对的主角&#xff0c;虽然时常有女诗人&#xff0c;却如流星闪过。 一直到宋代&#xff0c;李清照的横空出世&#xff0c;给文坛带来一股清风。 她被誉为“千古第一才女”&#xff0c;她的诗词可柔美、可刚毅。 有人将她与李煜…

android studio RadioButton单选按钮

1.定义 <!--单选按钮--> <TextViewandroid:layout_marginTop"10dp"android:layout_width"match_parent"android:layout_height"wrap_content"android:text"请选择你的性别&#xff1a;"> </TextView> <RadioGrou…

3年外包终上岸,我只能说这类公司能不去就不去····

我大学学的是计算机专业&#xff0c;毕业的时候&#xff0c;对于找工作比较迷茫&#xff0c;也不知道当时怎么想的&#xff0c;一头就扎进了一家外包公司&#xff0c;一干就是3年。现在终于跳槽到了互联网公司了&#xff0c;我想说的是&#xff0c;但凡有点机会&#xff0c;千万…

广州华锐互动:超写实3d虚拟数字人为多行业场景赋能

超写实3d虚拟数字人是一种高度逼真的虚拟人物形象&#xff0c;其主要是通过计算机技术对人物形象进行建模、渲染和动画制作&#xff0c;达到和真实人物相似的效果。在现代科技的推动下&#xff0c;超写实3d虚拟数字人已经得到广泛应用。 以下是其常见的实用功能&#xff1a; 商…

20230425-VS2019-在线安装C++17

一、软件环境 windows 10 x64 21H2 19044.2846 最后升级时间&#xff1a;2023-04-25 11:19:33 二、下载VS2019 官方下载&#xff1a; 打开官网&#xff1a; https://visualstudio.microsoft.com/zh-hans/vs/older-downloads 展开VS2019&#xff0c;选择【下载】&#xff…

数据流重定向(>,>>)(<,<<)(2>,2>>)

文章目录 数据流重定向什么是数据流重定向标准输出(>,>>)与标准错误输出(2>,2>>)/dev/null垃圾桶黑洞设备与特殊写法标准输入&#xff1a;<与<< 为什么要使用命令输出重定向呢&#xff1f;案例 数据流重定向 数据流重定向由字面意思来看&#xff0…

本地缓存解决方案Caffeine | Spring Cloud 38

一、Caffeine简介 Caffeine是一款高性能、最优缓存库。Caffeine是受Google guava启发的本地缓存&#xff08;青出于蓝而胜于蓝&#xff09;&#xff0c;在Cafeine的改进设计中借鉴了 Guava 缓存和 ConcurrentLinkedHashMap&#xff0c;Guava缓存可以参考上篇&#xff1a;本地缓…

分布式消息队列Kafka(三)- 服务节点Broker

1.Kafka Broker 工作流程 &#xff08;1&#xff09;zookeeper中存储的kafka信息 ​ 1&#xff09;启动 Zookeeper 客户端。 [zrclasshadoop102 zookeeper-3.5.7]$ bin/zkCli.sh ​ 2&#xff09;通过 ls 命令可以查看 kafka 相关信息。 [zk: localhost:2181(CONNECTED) 2]…