二十三种设计模式(二十)--解释器模式

解释器模式(Interpret)

当我们需要在程序运行时, 通过配置筛选形成不同的规则, 从而让程序根据动态变化的规则得到我们期待的结果时, 并且这些规则本身比较通用, 只需要在程序运行时, 通过用户自行组合配置来实现某一个功能时, 就需要解释器模式.

简单讲, 解释器模式用来解决基本规则众多且固定, 运行中规则组合多变的场景

经典的解释器模式如计算器, 加, 减, 乘, 除, 百分比等这些基础规则, 去解决我们复杂的组合运算, 各种加减乘除运算组合嵌套.

当然, 实际生产中我们不会去写计算器这么无意义的项目.

现实场景中, 想象我们要开发一个电话机器人, 它每天负责根据我们提供的电话列表给海量的用户打电话, 我们在后台要建立一套用户筛选机制, 这个机制包括以下几项内容:

  1. 用户的咨询意向等级: ABCDE五个等级
  2. 年龄分段: 10-20, 20-30, 30-40, 40-50, 50+
  3. 性别: 男, 女
  4. 用户当时的心理咨询状态: 焦虑, 抑郁, 双向情感障碍, 安全型

所有的客户类型都会保存入数据库, 但是, 我们会根据如下条件组合快速地把符合我们期待的意向客户推送给客服人员去快速对接. 具体需求如下:

有时我需要筛选出20-30岁的男性抑郁状态有A级咨询意向的客户

有时我又需要找出焦虑的50+岁女性在C级以上的咨询意向的客户

针对这些需求, 我们组合条件时需要将条件用"或", "等于", "且"这些基本的操作规则连接起来, 实现最终的筛选

如下是代码实现:

先定义客户类, 包含客户各项属性, 这里用直接构造的方式初始化各项属性, 实际开发过程中会使用哈希表来动态地增删各项属性

java 复制代码
enum Sex {
    MAN,
    WOMEN
}

enum Age {
    AGE_10(10),
    AGE_20(20),
    AGE_30(30),
    AGE_40(40),
    AGE_50(50);

    private final int age;

    Age(int age) {
        this.age = age;
    }

    public int getAge() {
        return age;
    }
}

enum MentalState {
    ANXIOUS,
    DEPRESSION,
    SAFE,
    BIPOLAR_DISORDER
}

enum DesireLevel {
    A, B, C, D, E
}

// 记录客户分类信息, 解释器将根据这些信息组合条件确定是否推送
class Customer {
    private  final Sex sex;
    private  final int age;
    private  final MentalState mentalState;
    private  final DesireLevel desireLevel;

    Customer(Sex sex, int age, MentalState state, DesireLevel level) {
        this.sex = sex;
        this.age = age;
        this.mentalState = state;
        this.desireLevel = level;
    }

    public Sex getSex() {
        return sex;
    }

    public int getAge() {
        return age;
    }

    public MentalState getMentalState() {
        return mentalState;
    }

    public DesireLevel getDesireLevel() {
        return desireLevel;
    }
}

接下来定义解释器通用接口, 及各种原子解释器, 这里也是解释器模式的核心

在解释器实现的过程中, 需要了解Java函数式接口, 从而可以实现通配Customer中的getter方法,

避免每一个属性对应一个解释器

java 复制代码
// 最基础的解释器统一接口
interface Expression {
    boolean interpret(Customer customer);
}


// 相等关系解释器, 判断Customer对象中某个值是否等于期待的值
// 通过函数式接口通配Customer中的方法
class Equals<T> implements Expression {
    private Function<Customer, T> extractor;
    private T expected;
    Equals(Function<Customer, T> extractor, T expected) {
        this.extractor = extractor;
        this.expected = expected;
    }

    @Override
    public boolean interpret(Customer customer) {
        return expected.equals(extractor.apply(customer));
    }
}

// 逻辑与, 这里判断的是多个解释器只要有一个不成立, 整体就不成立
class And implements Expression {
    List<Expression> expressionList;

    And(List<Expression> expressionList) {
        this.expressionList = expressionList;
    }

    @Override
    public boolean interpret(Customer customer) {
        for (Expression expression : expressionList) {
            if (!expression.interpret(customer)) {
                return false;
            }
        }

        return true;
    }
}

// 逻辑或, 这里同样, 判断的是多个解释器, 只要有一个成立, 整体就成立
class Or implements Expression {
    List<Expression> expressionList;

    Or(List<Expression> expressionList) {
        this.expressionList = expressionList;
    }

    @Override
    public boolean interpret(Customer customer) {
        for (Expression expression: expressionList) {
            if (expression.interpret(customer)) {
                return true;
            }
        }
        return false;
    }
}

// 解释器规则可以灵活扩展
// 下面扩展一个"区间"解释器
class Between implements Expression {
    ToIntFunction<Customer> extractor;
    int min;
    int max;

    Between(ToIntFunction<Customer> extractor, int min, int max) {
        this.extractor = extractor;
        this.min = min;
        this.max = max;
    }

    @Override
    public boolean interpret(Customer customer) {
        int val = extractor.applyAsInt(customer);
        return val >= min && val <= max;
    }
}

最后定义一个规则容器, 用来承接各种解释器规则拼接后的结果

以及一个规则过滤器, 用来利用生成的规则, 过滤客户信息, 生成最终结果

java 复制代码
// 解释器规则自由组合后, 需要一个类来存储规则, 生成具体的规则实例
class Rule {
    private final Expression expression;

    Rule(Expression expression) {
        this.expression = expression;
    }

    public boolean match(Customer customer) {
        return expression.interpret(customer);
    }
}

// 根据Rule规则, 过滤筛选用户
class RuleEngine {
    public void filter(List<Customer> customers, Rule rule) {
        for(Customer customer: customers) {
            if (rule.match(customer)) {
                System.out.println("性别: " + customer.getSex()
                                    + " 年龄: " + customer.getAge()
                                    + " 情感状态: " + customer.getMentalState()
                                    + " 咨询倾向等级: " + customer.getDesireLevel());
            }
        }
    }
}

以上就是整个解释器模式的实现, 在最终main方法调用展示解释器模式如何灵活地拼凑规则

java 复制代码
import java.util.function.Function;
import java.util.function.ToIntFunction;
import java.util.List;
import java.util.ArrayList;

public class InterpretPattern {
    public static void main(String[] args) {
        // 先创建5个Customer并加入列表中
        Customer c1 = new Customer(Sex.MAN, Age.AGE_20.getAge(), MentalState.ANXIOUS, DesireLevel.A);
        Customer c2 = new Customer(Sex.MAN, Age.AGE_30.getAge(), MentalState.SAFE, DesireLevel.B);
        Customer c3 = new Customer(Sex.WOMEN, Age.AGE_30.getAge(), MentalState.DEPRESSION, DesireLevel.B);
        Customer c4 = new Customer(Sex.WOMEN, Age.AGE_50.getAge(), MentalState.SAFE, DesireLevel.E);
        Customer c5 = new Customer(Sex.MAN, Age.AGE_40.getAge(), MentalState.BIPOLAR_DISORDER, DesireLevel.A);

        ArrayList<Customer> customers = new ArrayList<>();
        customers.add(c1);
        customers.add(c2);
        customers.add(c3);
        customers.add(c4);
        customers.add(c5);

        // 通过组合条件来实现动态的动态的筛选功能
        // 条件1: 筛选情绪状态为SAFE的女性客户
        Expression groupSafeWomen = new And(List.of(
                new Equals<>(Customer::getMentalState, MentalState.SAFE),
                new Equals<>(Customer::getSex, Sex.WOMEN)
        ));
        Rule rule01 = new Rule(groupSafeWomen);

        // 条件2: 筛选男性客户
        Expression groupMan = new Equals<>(Customer::getSex, Sex.MAN);
        Rule rule02 = new Rule(groupMan);

        // 条件3: 筛选20-40岁之间情绪状态为BIPOLAR_DISORDER或DEPRESSION的, 咨询意向在A或B的用户
        Expression group_20_40 = new And(List.of(
                new Between(Customer::getAge,
                        Age.AGE_20.getAge(), Age.AGE_40.getAge()),
                new Or(List.of(
                        new Equals<>(Customer::getMentalState, MentalState.BIPOLAR_DISORDER),
                        new Equals<>(Customer::getMentalState, MentalState.DEPRESSION)
                )),
                new Or(List.of(
                        new Equals<>(Customer::getDesireLevel, DesireLevel.A),
                        new Equals<>(Customer::getDesireLevel, DesireLevel.B)
                ))
        ));

        Rule rule03 = new Rule(group_20_40);

        // 传入列表, 分别验证以上三种筛选规则
        RuleEngine ruleEngine = new RuleEngine();
        System.out.println("===> 规则一: 情绪状态为SAFE的女性客户");
        ruleEngine.filter(customers, rule01);

        System.out.println("===> 规则二: 男性客户");
        ruleEngine.filter(customers, rule02);

        System.out.println("===> 规则三: 20-40岁之间情绪状态为BIPOLAR_DISORDER或DEPRESSION的, 咨询意向在A或B的用户");
        ruleEngine.filter(customers, rule03);
    }
}

运行后输出结果

复制代码
===> 规则一: 情绪状态为SAFE的女性客户
性别: WOMEN 年龄: 50 情感状态: SAFE 咨询倾向等级: E
===> 规则二: 男性客户
性别: MAN 年龄: 20 情感状态: ANXIOUS 咨询倾向等级: A
性别: MAN 年龄: 30 情感状态: SAFE 咨询倾向等级: B
性别: MAN 年龄: 40 情感状态: BIPOLAR_DISORDER 咨询倾向等级: A
===> 规则三: 20-40岁之间情绪状态为BIPOLAR_DISORDER或DEPRESSION的, 咨询意向在A或B的用户
性别: WOMEN 年龄: 30 情感状态: DEPRESSION 咨询倾向等级: B
性别: MAN 年龄: 40 情感状态: BIPOLAR_DISORDER 咨询倾向等级: A
相关推荐
聊浮游24 分钟前
JAVA2026最新全套学习资料、学习路线
java·开发语言·jvm·mysql·spring·maven·idea
MacroZheng37 分钟前
完美替代 Navicat!这款内置 AI 的数据库工具,太香了!
java·后端·mysql
SamDeepThinking1 小时前
从REST到gRPC,一个API选型的思考框架
java·后端·程序员
Zane19941 小时前
接口都能写默认实现了,为什么还需要抽象类
java·后端
未秃头的程序猿1 小时前
读Spring AI源码的方法——不是硬啃,是带着问题去翻
java·后端·spring
用户3126874877201 小时前
别再 Thread.sleep 硬等了!并发工具类到底怎么选?
java
Java内核笔记1 小时前
万字长文剖析 Spring Boot 4 自动配置机制源码:从 @EnableAutoConfiguration 到条件装配
java·后端
evans在进步1 小时前
Spring MVC 核心机制详解:全局异常处理、请求跳转与数据绑定
java·spring·mvc
嘟嘟07172 小时前
TypeScript 工具类型通关:Pick、Omit、Partial、Record 区别与 Omit 等价实现一次讲清
设计模式·面试·typescript
weixin_BYSJ19872 小时前
springboot小区物业管理小程序---附源码45555
java·javascript·spring boot·python·django·flask·php