设计模式之策略模式_策略模式和状态模式

设计模式之策略模式_策略模式和状态模式概述在策略模式(StrategyPattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

概述

在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。

在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。

策略模式旨在解决不同逻辑下相同的对象执行不同策略的问题。

当我们遇到同一个方法,里面会根据需要多个逻辑的分支,分支里的行为都不同,但是都服务于同一个功能,这个时候就可以使用策略模式,将行为抽象为一个策略接口中的抽象方法,由接口的实现类——也就是策略类——去实现各中具体的行为。

策略模式也是一种比较常见且好用的设计模式,线程池的拒绝策略就使用了策略模式。

一、简单实现

简单的拿一个根据情况需要导出不同文件的接口举例:

public void exportFile(String type){
    if (type == "excel") {
        // 导出excel
    } else if (type == "word") {
        // 导出word
    } else if (type == "pdf") {
        // 导出pdf
    }else {
        throw new RuntimeException("错误的文件类型!");
    }
}

这些分支里目的都是导出文件,但是各自有各的实现代码。换而言之,这些不同逻辑分支下的代码只有行为是不同的。现在我们将导出方法抽象成为一个策略接口中的抽象方法,将每个逻辑分支的处理代码都抽成实现策略接口的各个策略类。

// 文件导出接口
public interface IExportFile(){
    void export();
}

// 实现类
public class ExportExcel implements IExportFile{
    @Override
   public void export() {
      // 导出excel
   }
}
public class ExportWord implements IExportFile{
    @Override
   public void export() {
      // 导出word
   }
}
public class ExportPdf implements IExportFile{
    @Override
   public void export() {
      // 导出pdf
   }
}

然后调用的时候直接将接口实现类作为参数传入:

// 改造原有方法,将策略接口的实现作为参数传入
public void exportFile(String type){
    IExportFile exportFile;
    if (type == "excel") {
        exportFile = new ExportExcel();
    } else if (type == "word") {
        exportFile = new ExportWord();
    } else if (type == "pdf") {
        exportFile = new ExportPdf();
    }else {
        throw new RuntimeException("错误的文件类型!");
    }
    exportFile.export();
}

我们可以看到,原本耦合在调用方法里的行为被解放出来了,通过为策略接口更换策略类,我们可以很方便的切换行为。

策略模式改进原有方法

二、策略池与上下文对象

策略池

根据上文的简单例子,我们将具体的策略通过策略接口与调用方法耦合了,但是我们不难发现,现在的实现仍然需要通过大量的 if-else 判断去选择执行策略

实际上,我们可以这么考虑,代码被封装到实现类里以后,实际上一个策略跟对应的判断条件实际上就是一种 key 和 value 之间的映射关系了,我们可以根据这个思路,换一个更简洁一些的方式去替换 if-else 的代码:比如将条件字段与策略类放入 Map 集合实现的一个策略池中,直接通过 key 去获取对应的策略类

还是基于上述导出接口的例子:

// 策略池
static Map<String,IExportFile> exportStrategyPool = new HashMap<>();
static {
    exportStrategyPool.put("excel", new ExportExcel());
    exportStrategyPool.put("word", new ExportWord());
    exportStrategyPool.put("pdf", new ExportPdf());
}

// 导出文件
public void exportFile(String type){
    if (!exportStrategyPool.containsKey(type)) {
        throw new RuntimeException("错误的文件类型!");
    }
    IExportFile exportFile = exportStrategyPool.get(type);
    exportFile.export();
}

我们可以看到,通过策略池,我们直接简化了大量的 if-else 代码。

实际上,考虑到实现类是无状态的,那么策略类和策略池都应该是单例的,因此,这里使用了饿汉式去创建策略池,这里同样有许多优化的地方:比如可以手动创建改为通过反射自动装填策略类;可以创建枚举类或者将条件作为常量来规范策略的对应关系;如果在 spring 项目中,也可以考虑通过 spring 去创建……

这些的都可以根据需求进行优化的,但是核心仍然是在调用前建立条件与策略的映射关系

策略池的实现

上下文对象

现在,出于优化 if-else 的原因,我们为导出方法加入了策略池,但是这个类的其他方法未必用得到,为此我们不妨将整个策略池和导出方法都封装到另一个单独的类里,只提供一个带条件参数的方法。现在策略池归上下文对象管理了,那么这个上下文对象也应该是单例的,就个人观点,单独放到一个独立的 Service 和对应一个实现类,由 spring 管理应该是比较合适的。

// FileExporter 屏蔽了方法的具体实现
public class FileExporter {

    private Map<String,IExportFile> exportStrategyPool;

    public FileExporter() {
        exportStrategyPool = new HashMap<>();

        exportStrategyPool.put("excel", FileExporter::exportExcel);
        exportStrategyPool.put("word", FileExporter::exportWord);
        exportStrategyPool.put("pdf", FileExporter::exportPdf);
    }

    // 导出文件
    public void exportFile(String type){
        if (!exportStrategyPool.containsKey(type)) {
            throw new RuntimeException("错误的文件类型!");
        }
        IExportFile exportFile = exportStrategyPool.get(type);
        exportFile.export();
    }
}

// 调用
new FileExporter().exportFile("word");

通过 FileExporter 这个承上启下的上下文对象,我们屏蔽了具体代码的实现,同时通过“人”去调用“行为”这个逻辑也更符合面向对象的思想。

上下文对象

三、配合函数式接口使用

策略模式+策略池的手段已经可以解决传统 if-else 的大多数问题了,但是他也随之带来了两个问题:

  • 一个方法要用到的策略会产生大量实现类;
  • 业务逻辑被分散到了各个实现类,无法方便的总览。

为此,JDK8 的函数式接口刚好能非常完美的解决这些痛点。

关于函数式接口,我已在JDK1.8新特性(三):Lambda表达式里介绍过了,这里就不再赘述,直接上手。

还是以上文的文件导出接口为例:

当我们完成这个功能以后,我们会在原来的基础上多处一个上下文对象,一个策略接口,以及 n 多个实现接口的策略类,一个策略对应一个策略实现类的问题是导致类数量膨胀的原因,因此我们可以将策略接口替换为函数式接口,这样就可以在需要的时直接通过 Lambda 表达式传入实现类,避免新建类

// 将原本的IExportFile改为函数式接口
@FunctionalInterface
public interface IExportFile {
    void export();
}

// 在策略池阶段直接放入匿名实现类
static Map<String,IExportFile> exportStrategyPool = new HashMap<>();
static {
    exportStrategyPool.put("excel", () -> System.out.println("excel"));
    exportStrategyPool.put("word", () -> System.out.println("excel"));
    exportStrategyPool.put("pdf", () -> System.out.println("excel"));
}

当然,实际工作中的方法肯定比区区一句 System.out.println()要复杂的多,我们不可能集中在策略池里写实现。这个问题也很好解决,我们可以将具体的方法抽出来放到一个实现类里,比 service 的实现类,或者上下文对象中:

public class FileExporter {

    private Map<String,IExportFile> exportStrategyPool;

    private FileExporter() {
        exportStrategyPool = new HashMap<>();

        exportStrategyPool.put("excel", this::exportExcel);
        exportStrategyPool.put("word", this::exportWord);
        exportStrategyPool.put("pdf", this::exportPdf);
    }

    // 导出文件
    public void exportFile(String type){
        if (!exportStrategyPool.containsKey(type)) {
            throw new RuntimeException("错误的文件类型!");
        }
        IExportFile exportFile = exportStrategyPool.get(type);
        exportFile.export();
    }
	
    // 导出的策略
    public void exportExcel() {
        System.out.println("导出excel");
    }
    public void exportWord() {
        System.out.println("导出word");
    }
    public void exportPdf() {
        System.out.println("导出pdf");
    }
}

现在,业务代码可以都放在一个类里面了,总览起来也非常方便。

四、总结

通过策略模式,我们可以做到:

  1. 通过将行为抽象为一个策略接口,具体的行为作为接口的实现类,来分离方法和逻辑分支中的代码;
  2. 通过策略池来避免大量的 if-else 判断;
  3. 通过将策略池和方法封装到上下文对象来对外部屏蔽底层的实现;

对于策略模式带来的策略类过多,业务逻辑分散的问题:

  1. 将策略接口改为函数式接口,省去创建实现类,直接通过 Lambda 表达式直接传入匿名实现类;
  2. 在上述基础上,将实现方法统一写在一个类里,策略池在创建时通过 Lambda 表达式把类中的方法传入策略池。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/170783.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • JavaScript 设计模式之组合模式

    JavaScript 设计模式之组合模式引我们知道地球和一些其他行星围绕着太阳旋转,也知道在一个原子中,有许多电子围绕着原子核旋转。我曾经想象,我们的太阳系也许是一个更大世界里的一个原子,地球只是围绕着太阳原子的一个电子。而我身上的每个原子又是一个星系,原子核就是这个星系中的恒星,电子是围绕着恒星旋转的行星。一个电子中也许还包含了另一个宇宙,虽然这个宇宙还不能被显微镜看到,但我相信它的存在。也许这个想法有些异想天开,但在程序设计中,…

    2022年7月12日
    16
  • 从零开始的Android:常见的UI设计模式「建议收藏」

    从零开始的Android:常见的UI设计模式「建议收藏」尽管Android允许您创建几乎任何可能需要的自定义视图或用户界面,但事实证明,在正确的情况下,有一些用户界面模式可以很好地适用于用户。在本教程中,您将学习其中的一些模式,以及它们如何通过在使用应用程序时创造出色的体验来帮助用户。1.主画面用户在打开应用程序时看到的第一个屏幕通常是最重要的。从这里开始,您的用户应该能够执行快速动作并继续前进,或者进一步深入到您的应用中以完善他们…

    2022年6月21日
    28
  • 23种设计模式(2):工厂方法模式

    23种设计模式(2):工厂方法模式

    2021年11月13日
    47
  • 设计模式——门面模式「建议收藏」

    设计模式——门面模式「建议收藏」今天我们继续来学习前面没有学完的结构型设计模式中的一种:门面模式。门面模式也是一种不太常用的设计模式。所以,我们今天依旧是了解为主,暂时不去深入的学习。概述门面模式:(FacadeDesignPattern)门面模式也叫外观模式,门面模式为子系统提供一组统一的接口,定义一组高层接口让子系统更易用。门面模式原理和实现也比较简单,应用场景也比较明确,主要在接口设计方面使用。何时使用:解决易用性问题。解决性能问题。解决分布式事务问题。UML类图:角色组成:门面角色:客户端调用这个

    2025年6月6日
    3
  • 23种常用设计模式的UML类图

    23种常用设计模式的UML类图23种常用设计模式的UML类图本文UML类图参考《HeadFirst设计模式》(源码)与《设计模式:可复用面向对象软件的基础》(源码)两书中介绍的设计模式与UML图。整理常用设计模式的类图,一

    2022年6月30日
    29
  • 设计模式之代理模式XXOO

    设计模式之代理模式XXOO定义代理模式可以分为两种,一种是静态代理,一种是动态代理。静态代理:代理类一般会持有一个被代理的对象引用,且对于不关心的方法全部委托给被代理的对象处理。自己处理关心的方法。这种代理方式是死板的,它不是在运行时动态创建,它就是硬编码,你代码编译前写的是什么,编译后就是什么。换句话就是你按下CTRL+S的那一刻,就会被代理对象生成一个不可动态改变的代理类。静态代理一般对于代理的对象是单个或者多个固定的类(数量不会太多)使用。效果会比动态代理要好。动态代理:动态代理又分为JDK动

    2022年7月17日
    13

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号