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

解释器模式(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
相关推荐
桦说编程11 分钟前
从 ForkJoinPool 的 Compensate 看并发框架的线程补偿思想
java·后端·源码阅读
躺平大鹅2 小时前
Java面向对象入门(类与对象,新手秒懂)
java
静水流深_沧海一粟3 小时前
04 | 别再写几十个参数的构造函数了——建造者模式
设计模式
StarkCoder3 小时前
从UIKit到SwiftUI的迁移感悟:数据驱动的革命
设计模式
初次攀爬者3 小时前
RocketMQ在Spring Boot上的基础使用
java·spring boot·rocketmq
花花无缺3 小时前
搞懂@Autowired 与@Resuorce
java·spring boot·后端
Derek_Smart4 小时前
从一次 OOM 事故说起:打造生产级的 JVM 健康检查组件
java·jvm·spring boot
NE_STOP5 小时前
MyBatis-mybatis入门与增删改查
java
孟陬8 小时前
国外技术周刊 #1:Paul Graham 重新分享最受欢迎的文章《创作者的品味》、本周被划线最多 YouTube《如何在 19 分钟内学会 AI》、为何我不
java·前端·后端
想用offer打牌8 小时前
一站式了解四种限流算法
java·后端·go