[UE5 设计模式] 解释器模式Interpreter Pattern


参考材料
1. 【UE4 设计模式】策略模式 Strategy Pattern
2. 解释器模式
3. 字节码

1. 概述

1.1 描述

$\cdot$ 解释器模式(Interpreter Pattern) 提供了评估语言的语法或表达式的方式, 它属于行为型模式.
$\\$ 解释器模式给定一个语言, 定义它的文法的一种表示, 并定义一个解释器, 这个解释器使用该表示来解释语言中的句子.
$\\$ 这种模式被用在SQL解析, 符号处理引擎等.

1.2 套路

$\cdot$ 实现方式:
$\\$ 1) 定义文法: 明确语言的终结符和非终结符.
$\\$ 2) 构建语法树: 根据语言的句子构建对应的语法树结构.
$\\$ 3) 创建环境类: 包含解释过程中所需的全局信息, 通常是一个HashMap.

$\cdot$ 关键代码:
$\\$ 1) 终结符与非终结符: 定义语言的文法结构.
$\\$ 2) 环境类: 存储解释过程中需要的外部环境信息.

$\cdot$ 结构:
$\\$ 解释器模式包含以下几个主要角色:
$\\$ 1) 抽象表达式(Abstract Expression): 定义了解释器的抽象接口, 声明了解释操作的方法, 通常是一个抽象类或接口.
$\\$ 2) 终结符表达式(Terminal Expression): 实现了抽象表达式接口的终结符表达式类, 用于表示语言中的终结符(如变量, 常量等), 并实现了对应的解释操作.
$\\$ 3) 非终结符表达式(Non-terminal Expression): 实现了抽象表达式接口的非终结符表达式类, 用于表示语言中的非终结符(如句子, 表达式等), 并实现了对应的解释操作.
$\\$ 4) 上下文(Context): 包含解释器之外的一些全局信息, 在解释过程中提供给解释器使用, 通常用于存储变量的值, 保存解释器的状态等.
$\\$ 5) 客户端(Client): 创建并配置具体的解释器对象, 并将需要解释的表达式传递给解释器进行解释.

1.3 使用场景

$\cdot$ 当某一特定类型的问题频繁出现, 并且可以通过一种简单的语言来表达这些问题的实例时.

1.4 优缺点

$\cdot$ 优点:
$\\$ 1) 可扩展性好: 容易添加新的解释表达式的方式.
$\\$ 2) 灵活性: 可以根据需要轻松扩展或修改文法.
$\\$ 3) 易于实现简单文法: 对于简单的语言, 实现起来相对容易.

$\cdot$ 缺点:
$\\$ 1) 使用场景有限: 只适用于适合使用解释的简单文法.
$\\$ 2) 维护困难: 对于复杂的文法, 维护和扩展变得困难.
$\\$ 3) 类膨胀: 可能会产生很多类, 每个文法规则对应一个类.
$\\$ 4) 递归调用: 解释器模式通常使用递归调用, 这可能难以理解和跟踪.

2. UE5实践

我们将创建一个接口FExpression和实现了FExpression接口的实体类. 定义作为上下文中主要解释器的FTerminalExpression类. 其他的类FOrExpression, FAndExpression用于创建组合式表达式, 最后我们演示使用FExpression类创建规则和演示表达式的解析.

2.1 步骤1

创建一个表达式接口.


#pragma once

#include "CoreMinimal.h"


class FExpression 
{
public:
	virtual bool Interpret(const FString& Context)
	{
		return false;
	}
};

2.2 步骤2

创建实现了上述接口的实体类.


#pragma once

#include "Expression.h"


class FTerminalExpression final : public FExpression
{
public:
	FTerminalExpression(const FString& InData) : Data(InData)
	{
	}

	virtual bool Interpret(const FString& Context) override
	{
		return Context.Contains(Data);
	}

private:
	FString Data;
};


#pragma once

#include "Expression.h"


class FOrExpression final : public FExpression
{
public:
	FOrExpression(const FExpression& InExpression1, const FExpression& InExpression2) : Expression1(InExpression1), Expression2(InExpression2)
	{
	}

	virtual bool Interpret(const FString& Context) override
	{
		return Expression1.Contains(Data) || Expression2.Contains(Data);
	}

private:
	FExpression Expression1;

	FExpression Expression2;
};


#pragma once

#include "Expression.h"


class FAndExpression final : public FExpression
{
public:
	FAndExpression(const FExpression& InExpression1, const FExpression& InExpression2) : Expression1(InExpression1), Expression2(InExpression2)
	{
	}

	virtual bool Interpret(const FString& Context) override
	{
		return Expression1.Contains(Data) && Expression2.Contains(Data);
	}

private:
	FExpression Expression1;

	FExpression Expression2;
};

2.3 步骤3

使用FExpression类来创建规则, 并解析它们.


#include "TerminalExpression.h"
#include "OrExpression.h"
#include "AndExpression.h"

#define BOOL_TO_STRING(Bool) Bool ? TEXT("true") : TEXT("false")


// 规则: Robert和John是男性
static FExpression GetMaleExpression()
{
	FExpression Robert = FTerminalExpression(TEXT("Robert"));
	FExpression John = FTerminalExpression(TEXT("John"));
	return FOrExpression(Robert, John);
}

// 规则: Julie是一个已婚的女性
static FExpression GetMarriedWomanExpression()
{
	FExpression Julie = FTerminalExpression(TEXT("Julie"));
	FExpression Married = FTerminalExpression(TEXT("Married"));
	return FOrExpression(Julie, Married);
}

int main()
{
	FExpression IsMale = MoveTemp(GetMaleExpression());
	FExpression IsMarried = MoveTemp(GetMarriedWomanExpression());

	UE_LOG(LogTemp, Log, TEXT("John is male? %s"), BOOL_TO_STRING(IsMale.Interpret("John"));
	UE_LOG(LogTemp, Log, TEXT("Julie is a married women? %s"), BOOL_TO_STRING(IsMarried.Interpret("Married Julie"));

	return 0;
}

发表回复

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