策略模式——时势造英雄

news2024/11/25 20:41:12

● 策略模式介绍

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

        针对这种情况,一种常规的方法时将多种算法写在一个类中。例如,需要提供多种排序算法,可以将这些算法写在一个类中,每一个方法对应具体的排序算法;当然,也可以将这些排序算法封装在一个统一的方法中,通过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/471887.html

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

相关文章

AppWeb 身份验证绕过漏洞 (CVE-2018-8715)

当前漏洞环境部署在vulhub,当前验证环境为vulhub靶场&#xff08;所有实验均为虚拟环境&#xff09; 实验环境&#xff1a;攻击机----kali 靶机&#xff1a;centos7 1、进入靶场&#xff0c;启动环境 2、访问AppWeb控制台&#xff1a;http://your-ip:8080 使用用户名、密码adm…

全景丨0基础学习VR全景制作,平台篇第14章:热点功能-漫游

大家好&#xff0c;欢迎观看蛙色VR官方——后台使用系列课程&#xff01; 后台功能位置示意 热点&#xff0c;指在全景作品中添加各种类型图标的按钮&#xff0c;引导用户通过按钮产生更多的交互&#xff0c;增加用户的多元化体验。 漫游热点&#xff0c;即场景切换热点&#…

Jetpack Navigation 源码(一)

当然要先看官网文档&#xff1a;https://developer.android.google.cn/guide/navigation/navigation-getting-started?hlzh-cn 先从FragmentContainerView 开始 首页它是一个View image.png 注意它的name属性 image.png name属性的fragment 是怎么添加到这个View的(当然fragme…

关于Maven,你真的了解它吗?

编译软件&#xff1a;IntelliJ IDEA 2019.2.4 x64 操作系统&#xff1a;win10 x64 位 家庭版 Maven版本&#xff1a;apache-maven-3.6.3 目录 一. Maven有哪些核心概念&#xff1f;1.1 Maven中的POM1.2 Maven约定的目录结构1.3 Maven生命周期1.4 Maven的插件和目标1.5 Maven中的…

【面试】MySQL事务的12连问

文章目录 前言1. 什么是数据库事务&#xff1f;2. 事务的四大特性3. 事务的隔离级别有哪些&#xff1f;MySQL的默认隔离级别是什么&#xff1f;4. Mysql为什么选择RR作为默认隔离级别&#xff1f;5. 很多大厂为什么选择RC数据库隔离级别&#xff1f;6. 并发场景&#xff0c;数据…

经典回归算法

回归的概念 回归方程&#xff1a; 写成矩阵&#xff1a; 核心问题&#xff0c;构建预测函数z来映射特征矩阵x和标签y的线性关系 预测的目标值&#xff0c;有连续值也有离散值 连续值&#xff0c;就直接预测输出就行离散值&#xff0c;需要在输出端加一个变换函数例如。Si…

入参校验产品化 schema

与规则引擎不同,规则面向技术, 传入data, 返回 所有异常字段和原因. 面向技术, 先有对象,再有规则, 如何通过交互来编写schema是个难题? 和json-schema区别: 思路上就是反过来的, 面相产品, schema可视化编辑器, 是面向结构设计. 现有模型,才有数据, 才可以编程. 基于配置…

Docker安装运行Nginx容器(纯步骤)

Docker安装Nginx容器并运行 本文章只有步骤&#xff0c;没有原理解释&#xff0c;只做平时学习提示。提前说明&#xff1a;由于nginx里的配置文件比较多&#xff0c;所以本文章不对此配置文件解释而且会有一些小问题&#xff0c;这个你酌情操作&#xff0c;但不影响你nginx容器…

化工厂5G+北斗RTK室外人员定位系统解决方案

化工厂的安全管理工作非常重要&#xff0c;为了确保员工的安全和提高生产效率&#xff0c;建议引入人员定位技术。下面给大家介绍化工厂5G北斗RTK室外人员定位系统解决方案。关于化工厂室内人员定位方案&#xff0c;可以参考我之前写的文章&#xff1a;化工厂5G蓝牙LoRa室内人员…

声网 Token 鉴权机制,以及常见的问题

Token鉴权是什么&#xff1f; Token也称为动态密钥&#xff0c;是在加入频道时用于校验用户权限的一组字符串&#xff1b;鉴权是指在用户访问你的系统前&#xff0c;对其进行身份校验。用户在使用声网服务&#xff0c;如加入音视频通话或登录信令系统时&#xff0c;声网会使用…

广州华锐互动:工厂园区数字孪生系统让企业管理更加高效便捷

随着信息技术的快速发展&#xff0c;企业管理越来越倾向于数字化和智能化&#xff0c;而数字孪生技术在这一领域中扮演着重要角色。 工厂园区数字孪生系统是一种基于数字孪生技术的智能化系统&#xff0c;可以模拟工厂生产过程&#xff0c;将真实场景数字化&#xff0c;并进行…

高阶数据结构 ——— 并查集

文章目录 并查集并查集的原理并查集的实现并查集的初始化查找元素所在的集合合并两个元素所在的集合获取并查集中集合的个数并查集的路径压缩元素的编号问题 并查集的题目省份的数量等式方程的可满足性 并查集 并查集是一种树型的数据结构&#xff0c;用于处理一些不相交集合的…

如何合理使用 Jetpack 组件开发 Android 项目?

Jetpack 是 Android 官方推出的一套开发库&#xff0c;其中包含众多的组件&#xff0c;可以让 Android 开发者更快更高效地开发应用程序。Jetpack 组件分为四大部分&#xff1a;架构、行为、UI 和基础组件。 下面详细阐述如何合理使用 Jetpack 组件开发 Android 项目。 1. 熟练…

openQA----基于openSUSE部署openQA

【原文链接】openQA----基于openSUSE部署openQA &#xff08;1&#xff09;下载 openqa-bootstrap 脚本并执行 cd /opt/ curl -s https://raw.githubusercontent.com/os-autoinst/openQA/master/script/openqa-bootstrap | bash -x&#xff08;2&#xff09;配置apache proxy…

Nestjs全网最佳翻译-概况-守卫-Guards

守卫 带上装饰器 Injectable() 并实现了 CanActivate 接口的类&#xff0c;就是守卫。 守护只做一件事情。他们根据运行时的某些条件&#xff08;如权限、角色、ACL等&#xff09;来决定一个给定的请求是否会被路由处理程序处理。这通常被称为授权。在传统的Express应用程序中…

浅析AI视频智能检测技术在城市管理中的场景应用

随着中国的城市建设和发展日益加快&#xff0c;城镇化过程中重建设、轻管理模式带来不少管理难点&#xff0c;传统城管模式存在违法问题多样、缺乏源头治理、业务协同难、取证手段单一等&#xff0c;人员不足问题进一步加剧管理难度。随着移动互联网、物联网、云计算、大数据、…

Vue3 全局实例上挂载属性方法

导语 在大多数开发需求中&#xff0c;我们有时需要将某个数据&#xff0c;或者某个函数方法&#xff0c;挂载到&#xff0c;全局实例身上&#xff0c;以便于&#xff0c;在项目全局的任何位置都能够调用其方法&#xff0c;或读取其数据。 在Vue2 中&#xff0c;我们是在 main.j…

【Unity URP】Rendering Debugger和可视化MipMap方案

写在前面 最近开始学习Unity性能优化&#xff0c;是结合了《Unity游戏优化》这本书和教程《Unity性能优化》第叁节——静态资源优化(3)——纹理的基础概念一起学习。在学习纹理优化部分时候遇到了问题&#xff0c;固定管线下Unity的Scene窗口有一个可视化Mipmap的渲染模式&…

ChatGPT实现数据结构转换

数据结构转换 在应用系统开发和维护中&#xff0c;经常会有配置数据或客户数据需要在不同的序列化结构中相互转换的需求。不同编程语言之前&#xff0c;对数据结构的偏好也不一样&#xff0c;比如 JavaScript 一般使用 JSON、Java 一般使用 XML、Ruby 一般使用 YAML、Golang 一…

搞懂 API , API 这些特点要记住

API 是现代软件开发和应用程序中的必要组成部分&#xff0c;它为企业和开发者提供了极大的便利和工作效率。不过&#xff0c;API 也有其不足之处。下面将在这篇文章中详细探讨 API 的优点和缺点。 优点&#xff1a; 简化数据访问和交互 API 消除了传统的数据集成方法&#x…