写点什么

Java 枚举与策略模式、函数式接口的结合:实现高内聚低耦合的设计

  • 2024-11-21
    北京
  • 本文字数:4182 字

    阅读完需:约 14 分钟

作者:京东物流 杨唯一

一、Java 枚举类

Java 枚举是一个特殊的类,一般表示一组常量,比如一年的 4 个季节,一年的 12 个月份,一个星期的 7 天,方向有东南西北等。


我们在业务需求开发中,通常会使用枚举来定义业务上的一组常量,那除了简单地定义常量之外,我们如何利用枚举来实现高内聚、低耦合的设计呢?下面介绍下枚举和策略模式、函数式接口的组合应用。

二、枚举+策略模式

首先介绍下策略模式,已经掌握策略模式的同学可以直接跳过此部分。

1、策略模式

策略模式(Strategy Pattern)属于对象的行为模式。其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。 其主要目的是通过定义相似的算法,替换 if else 语句写法,并且可以随时相互替换。


策略模式主要由这三个角色组成,环境角色(Context)、抽象策略角色(Strategy)和具体策略角色(ConcreteStrategy)。


•环境角色(Context):持有一个策略类的引用,提供给客户端使用。


•抽象策略角色(Strategy):这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。


•具体策略角色(ConcreteStrategy):包装了相关的算法或行为。



这里为了方便理解,我们就拿刚学习 Java 的时候使用计算方法来说吧。 在使用计算器进行计算的时候,会经常用到加减乘除方法。如果我们想得到两个数字相加的和,我们需要用到“+”符号,得到相减的差,需要用到“-”符号等等。虽然我们可以通过字符串比较使用 if/else 写成通用方法,但是计算的符号每次增加,我们就不得不加在原先的方法中进行增加相应的代码,如果后续计算方法增加、修改或删除,那么会使后续的维护变得困难。 但是在这些方法中,我们发现其基本方法是固定的,这时我们就可以通过策略模式来进行开发,可以有效避免通过 if/else 来进行判断,即使后续增加其他的计算规则也可灵活进行调整。


首先定义一个抽象策略角色,并拥有一个计算的方法。


interface CalculateStrategy {   int doOperation(int num1, int num2);}
复制代码


然后再定义加减乘除这些具体策略角色并实现方法。


代码如下:


class OperationAdd implements CalculateStrategy {    @Override     public int doOperation(int num1, int num2) {         return num1 + num2;     } }class OperationSub implements CalculateStrategy {    @Override     public int doOperation(int num1, int num2) {         return num1 - num2;    } }
class OperationMul implements CalculateStrategy { @Override public int doOperation(int num1, int num2) { return num1 * num2; } }
class OperationDiv implements CalculateStrategy { @Override public int doOperation(int num1, int num2) { return num1 / num2; } }
复制代码


最后在定义一个环境角色,提供一个计算的接口供客户端使用。 代码如下:


class CalculatorContext {     private CalculateStrategy strategy;        public CalculatorContext(CalculateStrategy strategy) {        this.strategy = strategy;     }     public int executeStrategy(int num1, int num2) {         return strategy.doOperation(num1, num2);     }}
复制代码


编写好之后,那么我们来进行测试。 测试代码如下:


public static void main(String[] args) {    int a=4,b=2;
CalculatorContext context = new CalculatorContext(new OperationAdd()); System.out.println("a + b = "+context.executeStrategy(a, b));
CalculatorContext context2 = new CalculatorContext(new OperationSub()); System.out.println("a - b = "+context2.executeStrategy(a, b));
CalculatorContext context3 = new CalculatorContext(new OperationMul()); System.out.println("a * b = "+context3.executeStrategy(a, b));
CalculatorContext context4 = new CalculatorContext(new OperationDiv()); System.out.println("a / b = "+context4.executeStrategy(a, b)); }
复制代码


输出结果:


 a + b = 6 a - b = 2  a * b = 8  a / b = 2
复制代码


上面这段是网上常见的对于策略模式的介绍和示例,但在这段测试代码中,我们对于策略的选择依然是在具体的调用处通过对 CalculatorContext 类构造器的传参去指定的,那么如何能动态地选择策略并将选择策略的具体逻辑抽取,就要用到枚举+策略这套组合拳啦。

2、枚举+策略模式的使用

定义策略模式的枚举类,并将具体策略的 bean 名称作为枚举类 strategy 的值;


在 getStrategyEnum 方法中,我们可以去实现选择策略的逻辑,将选择策略的判断逻辑内聚在枚举类中,与业务代码隔离,当然在具体业务中,我们对于策略选择的判断会更复杂,此处只是举个简单的例子说明下:


@Getterpublic enum CalculateStrategyEnum {        ADD("operationAdd"),        SUB("operationSub"),        MUL("operationMul"),    DIV("operationDiv");    private final String strategy;        CalculateStrategyEnum(String strategy) {        this.strategy = strategy;        }        public static CalculateStrategyEnum getStrategyEnum(String operationName) {                switch (operationName) {                        case "加法运算":                                return ADD;                        case "减法运算":                                return SUB;                        case "乘法运算":                                return MUL;                        case "除法运算":                                return DIV;                        default:                                return null;                }        }}
复制代码


策略实现类中加入 @Component 注解,将 bean 实例交给 spring 容器管理


@Componentclass OperationAdd implements CalculateStrategy {    @Override     public int doOperation(int num1, int num2) {         return num1 + num2;     } }
@Componentclass OperationSub implements CalculateStrategy { @Override public int doOperation(int num1, int num2) { return num1 - num2; } }
@Componentclass OperationMul implements CalculateStrategy { @Override public int doOperation(int num1, int num2) { return num1 * num2; } }
@Componentclass OperationDiv implements CalculateStrategy { @Override public int doOperation(int num1, int num2) { return num1 / num2; } }
复制代码


策略调用处:业务处理


@Serviceclass CalculateService {    // 注入所有策略类    @Resource    Map<String, CalculateStrategy> calculateStrategy;       public executeStrategy(String operationName, int num1, int num2) {           // 根据参数匹配对应的枚举实例        CalculateStrategyEnum strategy = CalculateStrategyEnum.getStrategyEnum(operationName);                // 获取对应bean执行策略算法        if (stategy != null) {            calculateStrategy.get(strategy.getStrategy()).doOperation(num1, num2)        }          } }
复制代码


当我们需要新增算法时,只需新增新的策略实现类和枚举实例,修改枚举类中的策略选择方法,无需入侵现有业务代码,实现了高内聚、低耦合,增强了系统的灵活性和可扩展性。

三、枚举+函数式接口

1、函数式接口

什么是函数式接口


函数式接口是只包含一个抽象方法的接口。但是默认方法和静态方法在此接口中可以定义多个。Java 中的函数式接口可以被用作 Lambda 表达式的目标类型。通过函数式接口,可以实现更简洁、更具可读性的代码,从而支持函数式编程的思想。


Java 中有一些内置的函数式接口,用于不同的用途:


1.Runnable: 用于描述可以在单独线程中执行的任务。


2.Callable: 类似于 Runnable,但可以返回执行结果或抛出异常。


3.Comparator: 用于比较两个对象的顺序。


4.Function: 接受一个参数并产生一个结果。


5.Predicate: 接受一个参数并返回一个布尔值,用于判断条件是否满足。


6.Supplier: 不接受参数,但返回一个值。


7.Consumer: 接受一个参数,无返回值。

2、枚举+函数式接口的使用

我们可以通过为枚举类设置函数式接口类型的属性,让枚举实例拥有一些特定的行为


例如下面的例子,通过 Runnable 接口为枚举实例赋予了对应的行为。


public enum ActionEnum {        RUN(ActionEnum::run),        JUMP(ActionEnum::jump),        SIT(ActionEnum::sit);            private final Runnable action;            ActionEnum(Runnable action) {                this.action = action;        }            public static void run() {                System.out.println("Running");        }            public static void jump() {                System.out.println("Jumping");        }            public static void sit() {                System.out.println("Sitting");        }}
复制代码


也可以根据具体行为是否消费参数,以及是否提供返回值来选择合适的接口


public enum ActionEnum {        RUN(ActionEnum::run),        JUMP(ActionEnum::jump),        SIT(ActionEnum::sit);            private final Supplier<String> action;            ActionEnum(Supplier<String> action) {                this.action = action;        }            public static String run() {                return "Running";        }            public static String jump() {                return "Jumping";        }            public static String sit() {                return "Sitting";        }}
复制代码


这样,我们可以将与枚举值强关联的一些行为封装到枚举类中,与其他业务代码隔离,实现高内聚、低耦合的设计。

引用:

策略模式介绍:


https://www.cnblogs.com/xuwujing/p/9954263.html

发布于: 刚刚阅读数: 4
用户头像

拥抱技术,与开发者携手创造未来! 2018-11-20 加入

我们将持续为人工智能、大数据、云计算、物联网等相关领域的开发者,提供技术干货、行业技术内容、技术落地实践等文章内容。京东云开发者社区官方网站【https://developer.jdcloud.com/】,欢迎大家来玩

评论

发布
暂无评论
Java 枚举与策略模式、函数式接口的结合:实现高内聚低耦合的设计_京东科技开发者_InfoQ写作社区