JAVA设计模式之策略模式详解

策略模式

1 策略模式概述

策略模式(strategy pattern)的原始定义是:定义一系列算法,将每一个算法封装起来,并使它们可以相互替换。策略模式让算法可以独立于使用它的客户端而变化。

其实我们在现实生活中常常遇到实现某种目标存在多种策略可供选择的情况,例如,出行旅游可以乘坐飞机、乘坐火车、骑自行车或自己开私家车等。

在这里插入图片描述

在软件开发中,经常会遇到这种情况,开发一个功能可以通过多个算法去实现,我们可以将所有的算法集中在一个类中,在这个类中提供多个方法,每个方法对应一个算法, 或者我们也可以将这些算法都封装在一个统一的方法中,使用if…else…等条件判断语句进行选择.但是这两种方式都存在硬编码的问题,后期需要增加算法就需要修改源代码,这会导致代码的维护变得困难.

比如网购,你可以选择工商银行、农业银行、建设银行等等,但是它们提供的算法都是一致的,就是帮你付款。

在这里插入图片描述

在软件开发中也会遇到相似的情况,当实现某一个功能存在多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能。

6.3.2 策略模式原理

在策略模式中可以定义一些独立的类来封装不同的算法,每一个类封装一种具体的算法,在这里每一个封装算法的类都可以被称为一种策略,为了保证这些策略在使用时具有一致性,一般会提供一个抽象的策略类来做算法的声明.而每种算法对应一个具体的策略类.

在这里插入图片描述

策略模式的主要角色如下:

  • 抽象策略(Strategy)类:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
  • 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现或行为。
  • 环境或上下文(Context)类:是使用算法的角色, 持有一个策略类的引用,最终给客户端调用。

6.3.3 策略模式实现

策略模式的本质是通过Context类来作为中心控制单元,对不同的策略进行调度分配。

/**
 * 抽象策略类
 **/
public interface Strategy {

    void algorithm();
}

public class ConcreteStrategyA implements Strategy {

    @Override
    public void algorithm() {
        System.out.println("执行策略A");
    }
}

public class ConcreteStrategyB implements Strategy {

    @Override
    public void algorithm() {
        System.out.println("执行策略B");
    }
}

/**
 * 环境类
 **/
public class Context {

    //维持一个对抽象策略类的引用
    private Strategy strategy;

    public Context(Strategy strategy) {
        this.strategy = strategy;
    }

    //调用策略类中的算法
    public void algorithm(){
        strategy.algorithm();
    }
}

public class Client {

    public static void main(String[] args) {


        Strategy strategyA  = new ConcreteStrategyA();
        Context context = new Context(strategyA); //可以在运行时指定类型,通过配置文件+反射机制实现
        context.algorithm();
    }
}

6.3.4 策略模式应用实例

面试问题: 如何用设计模式消除代码中的if-else

物流行业中,通常会涉及到EDI报文(XML格式文件)传输和回执接收,每发送一份EDI报文,后续都会收到与之关联的回执(标识该数据在第三方系统中的流转状态)。

这里列举几种回执类型:MT1101、MT2101、MT4101、MT8104,系统在收到不同的回执报文后,会执行对应的业务逻辑处理。我们就业回执处理为演示案例

  1. 不使用设计模式
  • 回执类
/**
 * 回执信息
 **/
public class Receipt {

    private String message; //回执信息

    private String type; //回执类型(MT1101、MT2101、MT4101、MT8104)

    public Receipt() {
    }

    public Receipt(String message, String type) {
        this.message = message;
        this.type = type;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }
}
  • 回执生成器
public class ReceiptBuilder {

    public static List<Receipt> genReceiptList(){
        //模拟回执信息
        List<Receipt> receiptList = new ArrayList<>();
        receiptList.add(new Receipt("MT1101回执","MT1011"));
        receiptList.add(new Receipt("MT2101回执","MT2101"));
        receiptList.add(new Receipt("MT4101回执","MT4101"));
        receiptList.add(new Receipt("MT8104回执","MT8104"));

        //......
        return receiptList;
    }

}
  • 客户端
public class Client {

    public static void main(String[] args) {

        List<Receipt> receiptList = ReceiptBuilder.genReceiptList();

        //循环判断
        for (Receipt receipt : receiptList) {
            if("MT1011".equals(receipt.getType())){
                System.out.println("接收到MT1011回执!");
                System.out.println("解析回执内容");
                System.out.println("执行业务逻辑A"+"\n");
            }else if("MT2101".equals(receipt.getType())){
                System.out.println("接收到MT2101回执!");
                System.out.println("解析回执内容");
                System.out.println("执行业务逻辑B"+"\n");
            }else if("MT4101".equals(receipt.getType())) {
                System.out.println("接收到MT4101回执!");
                System.out.println("解析回执内容");
                System.out.println("执行业务逻辑C"+"\n");
            }else if("MT8104".equals(receipt.getType())) {
                System.out.println("接收到MT8104回执!");
                System.out.println("解析回执内容");
                System.out.println("执行业务逻辑D");
            }

            //......
        }
    }
}
  1. 使用策略模式进行优化

通过策略模式, 将所有的if-else分支的业务逻辑抽取为各种策略类,让客户端去依赖策略接口,保证具体策略类的改变不影响客户端.

  • 策略接口
/**
 * 回执处理策略接口
 **/
public interface ReceiptHandleStrategy {

    void handleReceipt(Receipt receipt);
}

  • 具体策略类
public class Mt1011ReceiptHandleStrategy implements ReceiptHandleStrategy {

    @Override
    public void handleReceipt(Receipt receipt) {
        System.out.println("解析报文MT1011: " + receipt.getMessage());
    }
}

public class Mt2101ReceiptHandleStrategy implements ReceiptHandleStrategy {

    @Override
    public void handleReceipt(Receipt receipt) {
        System.out.println("解析报文MT2101: " + receipt.getMessage());
    }
}

......
  • 策略上下文类(策略接口的持有者)
/**
 * 上下文类,持有策略接口
 **/
public class ReceiptStrategyContext {

    private ReceiptHandleStrategy receiptHandleStrategy;

    public void setReceiptHandleStrategy(ReceiptHandleStrategy receiptHandleStrategy) {
        this.receiptHandleStrategy = receiptHandleStrategy;
    }

    //调用策略类中的方法
    public void handleReceipt(Receipt receipt){
        if(receipt != null){
            receiptHandleStrategy.handleReceipt(receipt);
        }
    }
}
  • 策略工厂
public class ReceiptHandleStrategyFactory {

    public ReceiptHandleStrategyFactory() {
    }

    //使用Map集合存储策略信息,彻底消除if...else
    private static Map<String,ReceiptHandleStrategy> strategyMap;

    //初始化具体策略,保存到map集合
    public static void init(){
        strategyMap = new HashMap<>();
        strategyMap.put("MT1011",new Mt1011ReceiptHandleStrategy());
        strategyMap.put("MT2101",new Mt2101ReceiptHandleStrategy());
    }

    //根据回执类型获取对应策略类对象
    public static ReceiptHandleStrategy getReceiptHandleStrategy(String receiptType){
        return strategyMap.get(receiptType);
    }
}
  • 客户端
public class Client {

    public static void main(String[] args) {

        //模拟回执
        List<Receipt> receiptList = ReceiptBuilder.genReceiptList();


        //策略上下文
        ReceiptStrategyContext context = new ReceiptStrategyContext();

        //策略模式将策略的 定义、创建、使用这三部分进行了解耦
        for (Receipt receipt : receiptList) {
            //获取置策略
            ReceiptHandleStrategyFactory.init();
            ReceiptHandleStrategy strategy = ReceiptHandleStrategyFactory.getReceiptHandleStrategy(receipt.getType());
            //设置策略
            context.setReceiptHandleStrategy(strategy);
            //执行策略
            context.handleReceipt(receipt);
        }
    }
}

经过上面的改造,我们已经消除了if-else的结构,每当新来了一种回执,只需要添加新的回执处理策略,并修改ReceiptHandleStrategyFactory中的Map集合。如果要使得程序符合开闭原则,则需要调整ReceiptHandleStrategyFactory中处理策略的获取方式,通过反射的方式,获取指定包下的所有IReceiptHandleStrategy实现类,然后放到字典Map中去.

5 策略模式总结

  1. 策略模式优点:
  • 策略类之间可以自由切换

    由于策略类都实现同一个接口,所以使它们之间可以自由切换。

  • 易于扩展

    增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合“开闭原则“

  • 避免使用多重条件选择语句(if else),充分体现面向对象设计思想。

  1. 策略模式缺点:
  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
  • 策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。
  1. 策略模式使用场景
  • 一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。

    策略模式最大的作用在于分离使用算法的逻辑和算法自身实现的逻辑,这样就意味着当我们想要优化算法自身的实现逻辑时就变得非常便捷,一方面可以采用最新的算法实现逻辑,另一方面可以直接弃用旧算法而采用新算法。使用策略模式能够很方便地进行替换。

  • 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。

    在实际开发中,有许多算法可以实现某一功能,如查找、排序等,通过 if-else 等条件判断语句来进行选择非常方便。但是这就会带来一个问题:当在这个算法类中封装了大量查找算法时,该类的代码就会变得非常复杂,维护也会突然就变得非常困难。虽然策略模式看上去比较笨重,但实际上在每一次新增策略时都通过新增类来进行隔离,短期虽然不如直接写 if-else 来得效率高,但长期来看,维护单一的简单类耗费的时间其实远远低于维护一个超大的复杂类。

  • 系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构。

    如果我们不希望客户知道复杂的、与算法相关的数据结构,在具体策略类中封装算法与相关数据结构,可以提高算法的保密性与安全性.

设计原则和思想其实比设计模式更加的普适和重要,掌握了代码的设计原则和思想,我们自然而然的就可以使用到设计模式,还有可能自己创建出一种新的设计模式.

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

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

相关文章

Netty应用(一) 之 NIO概念 基本编程

目录 第一章 概念引入 1.分布式概念引入 第二章 Netty基础 - NIO 1.引言 1.1 什么是Netty&#xff1f; 1.2 为什么要学习Netty&#xff1f; 2.NIO编程 2.1 传统网络通信中开发方式及问题&#xff08;BIO&#xff09; 2.1.1 多线程版网络编程 2.1.2 线程池版的网络编程…

6.JavaScript中赋值运算符,自增运算符,比较运算符,逻辑运算符

赋值运算符 就是简单的加减乘除&#xff0c;没啥可说的这里直接上代码比较好 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><…

一文彻底搞懂Kafka如何保证消息不丢失

文章目录 1. kafka 架构2. producer端是如何保证数据不丢失的2.1 同步发送2.2 异步发送2.3 批量发送 3. consumer端是如何保证数据不丢失的3.1 手动提交3.2 幂等性消费 4. broker端是如何保证数据不丢失的4.1 副本机制4.2 ISR机制4.3 刷盘机制 1. kafka 架构 Producer&#xff…

redis的主从配置模拟(一主双从)

目录 先来给大家扩展机道面试官经常会问到关于redis的题 一、redis有哪些好处 二、redis相比memcached有哪些优势 三、redis常见性能问题和解决方案 四、redis集群的工作原理 五、redis主从的原理 redis的主从配置模拟&#xff08;一主双从&#xff09; 一、准备环境 …

二级建造师试题答案?学生党都在用的6款搜题工具来了 #学习方法#学习方法#微信

作为大学生&#xff0c;我们应该善于利用各种学习工具&#xff0c;提高学习效率和质量。 1.灵兔搜题 这是一个公众号 包含大学网课、课后教材、选修课、mooc慕课及各类职业资格证、学历提升考试、公务员考试等常见题库。 下方附上一些测试的试题及答案 1、Uri主要由三部分组…

spark sql上线前的调试工作实现

背景 每个公司应该都有大数据的平台的吧&#xff0c;平台的作用就是可以在上面执行各种spark sql以及定时任务&#xff0c;不过一般来说&#xff0c;由于这些spark sql的上线不经过测试&#xff0c;所以可能会影响到生产的数据&#xff0c;这种情况下大数据平台提供一个上线前…

Blazor Wasm Google 登录

目录: OpenID 与 OAuth2 基础知识Blazor wasm Google 登录Blazor wasm Gitee 码云登录Blazor SSR/WASM IDS/OIDC 单点登录授权实例1-建立和配置IDS身份验证服务Blazor SSR/WASM IDS/OIDC 单点登录授权实例2-登录信息组件wasmBlazor SSR/WASM IDS/OIDC 单点登录授权实例3-服务端…

Docker 有哪些常见的用途?

Docker 是一种容器化技术&#xff0c;它允许应用程序在不同的环境之间具有一致的运行环境。这使得 Docker 在开发和运维领域中非常受欢迎&#xff0c;因为它简化了应用程序的部署和管理。以下是 Docker 的一些常见用途&#xff1a; 快速部署应用程序 Docker 允许开发人员和运…

Mysql Day04

mysql体系结构 连接层服务层引擎层&#xff08;索引&#xff09;存储层 存储引擎 存储引擎是基于表建立的&#xff0c;默认是innoDB show create table tb; 查看当前数据库支持的存储引擎 show engines; InnoDB 特点 DML&#xff08;数据增删改&#xff09;遵循ACID模…

c++之说_11|自定义类型 enum(枚举)与enumclass (c11新枚举)

至于枚举 会用就行 至少目前我感觉没什么太多问题 enum 被称为无作用域枚举 &#xff0c; enumclass / enumstruct 被称为有作用域枚举 看到了吧 语法规则 和 struct 差不多 只不过枚举成员 只是一个标志 它本质是数值 从上到下 下面的数根据上面的数 加 1 也可以直接…

Codeforces Round 923 (Div. 3) C. Choose the Different Ones(Java)

比赛链接&#xff1a;Round 923 (Div. 3) C题传送门&#xff1a;C. Choose the Different Ones! 题目&#xff1a; ** Example** ** input** 6 6 5 6 2 3 8 5 6 5 1 3 4 10 5 6 5 6 2 3 4 5 6 5 1 3 8 10 3 3 3 4 1 3 5 2 4 6 2 5 4 1 4 7 3 4 4 2 1 4 2 2 6 4 4 2 1 5 2 3 …

决策树之scikit-learn

实例 from sklearn.datasets import load_iris from sklearn import tree import matplotlib.pyplot as plt# Load iris dataset iris load_iris() X, y iris.data, iris.target# Fit the classifier clf tree.DecisionTreeClassifier() clf clf.fit(X, y)# Plot the deci…

【FPGA】Verilog:奇偶校验位发生器 | 奇偶校验位校验器

目录 0x00 奇偶校验位发生器 0x01 奇偶校验位校验器 0x02 错误检测器和纠错器

16.1 Spring框架_SpringIoC容器与Bean管理(❤❤❤❤)

16.1 Spring框架_SpringIoC容器与Bean管理 1. Spring IOC1.1 IoC控制反转 1. Spring IOC 1.1 IoC控制反转 需要自己查找3种苹果的特色,从而选择符合自己的需求 告诉水果店老板自己的口味,由老板推荐哪种苹果,省去自己查询水果特点 在java中,各种水果就是各种对象,买水果就是创…

【CC++】内存管理1:new + delete

前言 之前我们学习过C语言中的内存管理&#xff08;各种函数&#xff09;今天我们来学习C中的内存管理 引入 我们先来看下面的一段代码和相关问题 int globalVar 1; static int staticGlobalVar 1; void Test() {static int staticVar 1;int localVar 1;int num1[10] {…

leetcode:63.不同路径二

dp数组含义&#xff1a;由初始位置到最终位置路径个数 递推公式&#xff1a;如果没有障碍再进行递推公式 初始化&#xff1a;1.若起始位置和终止位置有障碍路径个数为0 2.dp[i][0] 1和dp[0][j] 1的for循环条件都需要加上一个and dp[i][0] 0和and dp[0][j] 0. 3.遍历顺序…

在屏蔽任何FRP环境下从零开始搭建安全的FRP内网穿透服务

背景 本人目前在境外某大学读博&#xff0c;校园网屏蔽了所有内网穿透的工具的数据包和IP访问&#xff0c;为了实现在家也能远程访问服务器&#xff0c;就不得不先开个学校VPN&#xff0c;再登陆。我们实验室还需要访问另一个大学的服务器&#xff0c;每次我都要去找另一个大学…

Flink基础篇|001_Flink是什么

&#x1f4eb; 作者简介&#xff1a;「六月暴雪飞梨花」&#xff0c;专注于研究Java&#xff0c;就职于科技型公司后端工程师 &#x1f3c6; 近期荣誉&#xff1a;华为云云享专家、阿里云专家博主、腾讯云优秀创作者 &#x1f525; 三连支持&#xff1a;欢迎 ❤️关注、&#x…

监控概述、安装zabbix、配置zabbixagent、添加被控端主机、常用监控指标、自定义监控项

监控概述 对服务的管理&#xff0c;不能仅限于可用性。 还需要服务可以安全、稳定、高效地运行。 监控的目的&#xff1a;早发现、早治疗。 被监控的资源类型&#xff1a; 公开数据&#xff1a;对外开放的&#xff0c;不需要认证即可获取的数据私有数据&#xff1a;对外不开…

分享关闭Windows自动更新的六种方法。

方法一&#xff1a;禁用Windows Update服务 同时按下键盘的“WinR”键&#xff0c;打开“运行”窗口&#xff0c;输入“services.msc”并点击“确定”。 在打开的服务列表中找到“Windows Update”选项&#xff0c;双击打开其属性窗口。 在“启动类型”下拉菜单中选择“禁用”…