我所理解的设计模式系列·第21篇·基于解释器模式实现电商系统会员成长体系

2022/02/20 设计模式 共 4510 字,约 13 分钟

解释器模式(Interpreter pattern)是一种不太常见的设计模式,但事实上在某些需求的实现过程中,我们或多或少会借鉴到解释器模式的思想,今天就用支付宝的会员成长体系来演示解释器模式的应用场景。

design-patttern-21-解释器模式-封面

1. 定义

解释器模式是一种行为型设计模式,它的定义是:“给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。

为什么要定义一门“语言”?需要注意的是,这里的语言可能没有大到编程语言的地步,可能只是指一种通用规则。解释器模式可能跟模板模式的思想有些类型,当出现某些问题是重复的,可被抽象时,我们可以基于解释器模式来定义一种规则,这个规则可以用来解决一些逻辑相近的问题。

2. 类图

在解释器模式中,有这样几种角色:

  • 抽象表达式角色(Abstract Expression):定义解释器的抽象接口、提供解释器解释方法 interpret
  • 终结符表达式角色(Terminal Expression):抽象表达式角色的子类,实现了自定义语法中与终结符有关的操作,相当于递归中的终结递归条件或者树结构中的叶子结点
  • 非终结符表达式角色(Nonterminal Expression):抽象表达式角色的子类,实现了自定义语法中与非终结符有关的操作,相当于树结构中的非叶子结点
  • 上下文角色(Context):包含了解释器需要的上下文数据或公共抽象的功能

解释器模式的类图如下:

design-patttern-21-解释器模式-1-类图

3. 示例

先来简单说一下支付宝的会员等级成长体系,会员的等级是由会员的成长值决定,其门槛如下表:

等级对应升级门槛
大众会员0
黄金会员2000
铂金会员6000
钻石会员18000

关于成长值的获取也有一套自己的规则,当然这不在本文的探讨范围内,故此不做过多描述。

为了能更好的描述解释器模式,我在支付宝的会员成长体系的门槛基础上再加点佐料:

等级对应升级门槛1(成长值)对应升级门槛2(累计消费金额)
大众会员00
黄金会员20001000
铂金会员60003000
钻石会员180009000

拿黄金会员来做下解释:只有当会员成长值大于等于2000,同时其消费金额大于等于1000时,会员才能够升级成为黄金会员。

下面就用代码来描述上述业务。

首先定义抽象解释器、成长值和累计消费金额两类终结符表达式角色以及非终结符表达式角色,代码如下:

public interface AbstractExpression {
    boolean interpreter(Context info);
}

// 成长值解释器,终结符表达式角色
public class ExperienceTerminalExpression implements AbstractExpression {
    @Override
    public boolean interpreter(Context info) {
        // 判断用户当前成长值是否大于目标等级的成长值门槛
        return info.getMemberContext().getExperienceAmount() > info.getTargetLevelContext().getExperienceAmount();
    }
}

// 累计消费金额解释器,终结符表达式角色
public class ConsumeTerminalExpression implements AbstractExpression {
    @Override
    public boolean interpreter(Context info) {
        // 判断用户当前累计消费金额是否大于目标等级的累计消费金额门槛
        return info.getMemberContext().getConsumeAmount() > info.getTargetLevelContext().getConsumeAmount();
    }
}

// 非终结符表达式
public class NonTerminalExpression implements AbstractExpression {

    private AbstractExpression expression1;
    private AbstractExpression expression2;

    public NonTerminalExpression(AbstractExpression expression1, AbstractExpression expression2) {
        this.expression1 = expression1;
        this.expression2 = expression2;
    }

    @Override
    public boolean interpreter(Context info) {
        // 两个条件都满足才返回 true
        return expression1.interpreter(info) && expression2.interpreter(info);
    }
}

然后定义上下文角色,包括会员上下文(存储会员当前成长值及累计消费金额数据),目标等级上下文(存储目标等级的成长值门槛和累计消费金额门槛),其代码如下:

public class MemberContext {
    // 成长值
    private Integer experienceAmount;
    // 累计消费金额
    private Integer consumeAmount;

    public MemberContext(Integer experienceAmount, Integer consumeAmount) {
        this.experienceAmount = experienceAmount;
        this.consumeAmount = consumeAmount;
    }

		// 省略 setter/getter 方法
}

public class TargetLevelContext {
    // 成长值
    private Integer experienceAmount;
    // 累计消费金额
    private Integer consumeAmount;
    // 等级名称
    private String levelName;

    public TargetLevelContext(Integer experienceAmount, Integer consumeAmount, String levelName) {
        this.experienceAmount = experienceAmount;
        this.consumeAmount = consumeAmount;
        this.levelName = levelName;
    }

		// 省略 setter/getter 方法
}

// 上下文角色
public class Context {
    // 用户上下文
    private MemberContext memberContext;
    // 目标等级上下文
    private TargetLevelContext targetLevelContext;
    // 解释器
    private AbstractExpression expression;

    public Context(MemberContext memberContext, TargetLevelContext targetLevelContext, AbstractExpression expression) {
        this.memberContext = memberContext;
        this.targetLevelContext = targetLevelContext;
        this.expression = expression;
    }

    // 判定方法
    public boolean doOperation() {
        return expression.interpreter(this);
    }

  	// 省略 setter/getter 方法
}

最后编写测试代码:

public class Test {
    public static void main(String[] args) {
        TargetLevelContext level1 = new TargetLevelContext(0, 0,"大众会员");
        TargetLevelContext level2 = new TargetLevelContext(2000, 1000,"黄金会员");
        TargetLevelContext level3 = new TargetLevelContext(6000, 3000,"铂金会员");
        TargetLevelContext level4 = new TargetLevelContext(18000, 9000,"钻石会员");
        // 目标等级列表
        List<TargetLevelContext> levels = new ArrayList<>();
        levels.add(level1);
        levels.add(level2);
        levels.add(level3);
        levels.add(level4);
        // 成长值6800,累计消费2000的会员
        MemberContext memberContext = new MemberContext(6800, 2000);

        // 解释器
        AbstractExpression expression = new NonTerminalExpression(new ExperienceTerminalExpression(), new ConsumeTerminalExpression());

        String levelName = "";
        for (TargetLevelContext levelContext:levels) {
            // 组装上下文,对所有等级进行迭代判定
            Context context = new Context(memberContext, levelContext, expression);
            boolean result = context.doOperation();
            if (result) {
                levelName = levelContext.getLevelName();
            } else {
                break;
            }
        }
        System.out.println("最终判定结果:成长值6800,累计消费2000的会员等级 = " + levelName);
    }
}

输出结果为:

最终判定结果:成长值6800,累计消费2000的会员等级 = 黄金会员

在当前会员成长体系的解释器模式实现下,解释器解释的内容就是会员等级的规则判定,当前业务中只有两个维度的判断,在实际采用 RFM 模型进行会员等级的计算时业务会更为复杂,规则深度也不会只有两层。

4. 使用场景

解释器模式的使用场景是:

  • 当某类问题重复出现,且可被用一种简单的语言来进行表达,同时其执行效率又是不需要太过关心时

5. 小结

本文讲述了解释器模式,它的定义是——给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。

解释器模式的优点是:

  • 具备可扩展性,解释器模式定义了一种语言(规则),只需要满足这种语言,可以扩展出任意的逻辑

解释器模式的缺点是:

  • 当语法规则较复杂时,可能会导致类膨胀
  • 由于使用递归、循环,可能会导致执行效率较低,栈帧非常深

6. 参考资料

最后,本文收录于个人语雀知识库: 我所理解的后端技术,欢迎来访。

文档信息

Search

    Table of Contents