12.解释器模式

12.行为型模式-解释器模式


12.1 基础知识

学习难度:5

使用频率:1

总分:2

定义

解释器模式(Interpreter Pattern)给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

说人话

解释器模式用于定义一种语言的文法,并提供解释器来解释语言中的句子。在解释器模式中,非终结表达式的解释方法调用其他表达式的解释方法进行操作。

结构图

实现步骤

  • 上下文类:包含需要解释的信息或一些解释信息相关的集合,并提供操作方法以供解释器使用。
  • 表达式接口:定义解释方法。
  • 终结符表达式类:实现抽象表达式接口,表示文法中的终结符。实现解释方法时会返回不可再操作的表达式。
  • 非终结符表达式类:实现抽象表达式接口,表示文法中的非终结符。通常会在类中定义其他表达式变量。实现解释方法时会对其他表达式调用解释方法并进行操作。
  • 客户端:创建上下文类,定义表达式树得到表达式对象并解释。

说明

解释器模式在实际应用中使用频率较低,通常用于特定领域的语言解析和处理,例如编译器、正则表达式解析等。


12.2 模版代码

上下文类

// 上下文类 包含要被解释的信息
class Context
{
    public string Input { get; set; }
    public string Output { get; set; }

    public Context(string input)
    {
        Input = input;
    }
}

表达式接口和终结非总结表达式类

// 表达式接口 定义了解释方法
interface IExpression
{
    void Interpret(Context context);
}

// 终结符表达式
class TerminalExpression : IExpression
{
    public void Interpret(Context context)
    {
        // 实现终结符表达式的解释逻辑
        context.Output = "终结符表达式";
    }
}

// 非终结符表达式类
class NonTerminalExpression : IExpression
{
    private IExpression leftExpression;
    private IExpression rightExpression;

    public NonTerminalExpression(IExpression left, IExpression right)
    {
        leftExpression = left;
        rightExpression = right;
    }

    public void Interpret(Context context)
    {
        // 实现非终结符表达式的解释逻辑,通常基于左右表达式的组合
        leftExpression.Interpret(context);
        rightExpression.Interpret(context);
        context.Output = $"非终结符表达式 [{leftExpression.GetType().Name} {rightExpression.GetType().Name}]";
    }
}

客户端

class Program
{
    static void Main(string[] args)
    {
        Context context = new Context("示例输入");

        // 创建两个终结符表达式和一个非终结符表达式
        IExpression terminalExpression1 = new TerminalExpression();
        IExpression terminalExpression2 = new TerminalExpression();
        IExpression nonTerminalExpression = new NonTerminalExpression(terminalExpression1, terminalExpression2);

        // 解释表达式
        nonTerminalExpression.Interpret(context);
        
        Console.WriteLine(context.Output);//非终结符表达式 [TerminalExpression TerminalExpression]
    }
}

12.3 CSharp实践

实践需求

使用解释器模式,模拟加减运算。

解释器接口和具体解释器类

// 抽象表达式接口
public interface IExpression
{
    int Interpret(Dictionary<string, int> variables);
}

// 终结符表达式类 - 变量表达式
public class VariableExpression : IExpression
{
    private string variableName;

    public VariableExpression(string name)
    {
        variableName = name;
    }

    public int Interpret(Dictionary<string, int> variables)
    {
        if (variables.ContainsKey(variableName))
        {
            return variables[variableName];
        }
        else
        {
            throw new InvalidOperationException($"变量 '{variableName}' 未找到。"); // 抛出未找到变量的异常
        }
    }
}

// 终结符表达式类 - 常量表达式
public class ConstantExpression : IExpression
{
    private int value;

    public ConstantExpression(int value)
    {
        this.value = value;
    }

    public int Interpret(Dictionary<string, int> variables)
    {
        return value;
    }
}

// 非终结符表达式类 - 加法表达式
public class AddExpression : IExpression
{
    private IExpression left;
    private IExpression right;

    public AddExpression(IExpression left, IExpression right)
    {
        this.left = left;
        this.right = right;
    }

    public int Interpret(Dictionary<string, int> variables)
    {
        return left.Interpret(variables) + right.Interpret(variables);
    }
}

// 非终结符表达式类 - 减法表达式
public class SubtractExpression : IExpression
{
    private IExpression left;
    private IExpression right;

    public SubtractExpression(IExpression left, IExpression right)
    {
        this.left = left;
        this.right = right;
    }

    public int Interpret(Dictionary<string, int> variables)
    {
        return left.Interpret(variables) - right.Interpret(variables);
    }
}

客户端

class Program
{
    static void Main(string[] args)
    {
        // 创建变量字典并赋值
        Dictionary<string, int> variables = new Dictionary<string, int>();
        variables["x"] = 10;
        variables["y"] = 5;

        // 创建表达式 x+20
        IExpression expression = new AddExpression(new VariableExpression("x"), new ConstantExpression(20));

        // 解释并计算表达式的值
        int result = expression.Interpret(variables);
        Console.WriteLine("结果: " + result); // 30

        // 创建另一个表达式 x+20的结果-5
        expression = new SubtractExpression(expression, new VariableExpression("y"));

        // 再次解释并计算表达式的值
        result = expression.Interpret(variables);
        Console.WriteLine("结果: " + result); // 25
    }
}

12.4 Unity实践

实践需求

使用解释器模式,模拟剑圣可以单个使用技能和使用连招组合。

输入信息相关类

//易大师输入信息抽象类
public abstract class YiInputInfo
{

}

// 表示易大师的单按键输入信息类
public class YiSingleInputInfo : YiInputInfo
{
    public KeyCode keyCode; // 存储单按键的 KeyCode

    // 构造函数,用于初始化单按键输入信息
    public YiSingleInputInfo(KeyCode keyCode)
    {
        this.keyCode = keyCode; // 通过构造函数接收按键信息并存储在类的成员变量中
    }
}

// 表示易大师的连招输入信息类
public class YiComboInputInfo : YiInputInfo
{
    public List<KeyCode> keyCodeList; // 存储连招输入的按键列表

    // 构造函数,用于初始化连招输入信息
    public YiComboInputInfo(List<KeyCode> keyCodeList)
    {
        this.keyCodeList = keyCodeList; // 通过构造函数接收按键列表信息并存储在类的成员变量中
    }
}

解释器相关类

// 表示易大师技能表达式的通用接口
public interface ISkillExpression
{
    // 执行技能表达式,接受一个 YiContext 参数,以便在执行中访问游戏对象和数据
    void Execute(YiContext yiContext);
}

// 表示易大师的技能表达式类,用于执行“阿尔法突袭”技能
public class AlphaStrikeExpression : ISkillExpression
{
    // 执行“阿尔法突袭”技能
    public void Execute(YiContext yiContext)
    {
        Debug.Log("无极剑圣使用了阿尔法突袭,造成大量伤害!");
        // 在此处可以添加执行 Alpha Strike 技能的伤害逻辑

        // 使用动画触发器来播放技能动画
        yiContext.yiAnimator.SetTrigger(yiContext.GetSkillExpressionKeyCode(this.GetType()).ToString());
    }
}

// 表示易大师的技能表达式类,用于执行“冥想”技能
public class MeditationExpression : ISkillExpression
{
    // 执行“冥想”技能
    public void Execute(YiContext yiContext)
    {
        Debug.Log("无极剑圣开始冥想,恢复生命值!");
        // 在此处可以添加执行 Meditation 技能的生命值恢复逻辑

        // 使用动画触发器来播放技能动画
        yiContext.yiAnimator.SetTrigger(yiContext.GetSkillExpressionKeyCode(this.GetType()).ToString());
    }
}

// 表示易大师的技能表达式类,用于执行“无极剑道”技能
public class WujuStyleExpression : ISkillExpression
{
    // 执行“无极剑道”技能
    public void Execute(YiContext yiContext)
    {
        Debug.Log("无极剑圣启动了无极剑道,攻击变成真实伤害!");
        // 在此处可以添加执行 Wuju Style 技能的效果逻辑,例如将攻击变成真实伤害

        // 使用动画触发器来播放技能动画
        yiContext.yiAnimator.SetTrigger(yiContext.GetSkillExpressionKeyCode(this.GetType()).ToString());
    }
}

// 表示易大师的连招技能表达式类,实现了 ISkillExpression 接口
public class ComboExpression : ISkillExpression
{
    public List<ISkillExpression> comboSkillExpressionList = new List<ISkillExpression>();

    // 构造函数,用于初始化连招技能表达式
    public ComboExpression(List<ISkillExpression> comboSkillExpressionList)
    {
        this.comboSkillExpressionList = comboSkillExpressionList; // 通过构造函数接收连招技能表达式列表并存储在类的成员变量中
    }

    // 执行连招技能
    public void Execute(YiContext yiContext)
    {
        Debug.Log("进入连招");
        string comboKeyCodeStr = null;

        // 遍历连招技能表达式列表
        foreach (var comboSkillExpressionItem in comboSkillExpressionList)
        {
            comboKeyCodeStr += yiContext.GetSkillExpressionKeyCode(comboSkillExpressionItem.GetType()) + " ";
            // comboSkillExpressionItem.Execute(yiContext);
        }

        // 使用协程执行连招技能
        MyTimer.GetInstance().StartCoroutine(ExecuteComboSkills(comboSkillExpressionList, yiContext));
        Debug.Log("连招是" + comboKeyCodeStr);
        // Debug.Log("退出连招");
    }

    // 协程,用于逐个执行连招技能
    IEnumerator ExecuteComboSkills(List<ISkillExpression> comboSkillExpressionList, YiContext yiContext)
    {
        // 遍历连招技能表达式列表
        foreach (var comboSkillExpressionItem in comboSkillExpressionList)
        {
            // 执行当前连招技能表达式
            comboSkillExpressionItem.Execute(yiContext);

            // 等待一定数量的帧
            for (int i = 0; i < 100; i++)
            {
                yield return null; // 等待一帧
            }
        }
    }
}

易大师技能系统上下文类

// 易大师技能系统上下文类
public class YiContext
{
    // 易大师游戏对象的预制体
    private GameObject yiPrefab = Resources.Load<GameObject>("Lesson30_行为型模式_解释器模式/Yi");

    // 易大师游戏对象
    private GameObject yiGameObject;

    // 易大师技能表达式与按键之间的映射字典
    private Dictionary<KeyCode, Type> yiSkillMappingDictionary;

    // 获取技能映射字典的访问器
    public Dictionary<KeyCode, Type> YiSkillMappingDictionary => yiSkillMappingDictionary;

    // 易大师的动画控制器
    public Animator yiAnimator;

    // 易大师技能系统上下文的构造函数
    public YiContext()
    {
        // 实例化易大师游戏对象
        yiGameObject = GameObject.Instantiate(yiPrefab);

        // 获取易大师的动画控制器
        yiAnimator = yiGameObject.GetComponent<Animator>();

        // 初始化技能映射字典,并将按键与对应的技能表达式类型关联
        yiSkillMappingDictionary = new Dictionary<KeyCode, Type>();
        yiSkillMappingDictionary.Add(KeyCode.Q, typeof(AlphaStrikeExpression));
        yiSkillMappingDictionary.Add(KeyCode.W, typeof(MeditationExpression));
        yiSkillMappingDictionary.Add(KeyCode.E, typeof(WujuStyleExpression));
    }

    // 执行给定的技能表达式
    public void ExecuteSkill(ISkillExpression skillExpression)
    {
        skillExpression.Execute(this);
    }

    // 检查易大师是否处于空闲状态
    public bool IsYiIdle()
    {
        foreach (var yiSkillMappingItem in yiSkillMappingDictionary)
        {
            // 如果有技能在执行中,则易大师不是空闲的
            if (yiAnimator.GetBool(yiSkillMappingItem.Key.ToString()))
            {
                return false;
            }
        }

        // 所有技能都未在执行中,易大师被认为是空闲的
        return true;
    }

    // 获取与给定技能表达式类型关联的按键
    public KeyCode GetSkillExpressionKeyCode(Type skillExpressionType)
    {
        foreach (var yiSkillMappingItem in yiSkillMappingDictionary)
        {
            // 如果找到与给定技能表达式类型匹配的项,返回关联的按键
            if (skillExpressionType == yiSkillMappingItem.Value)
                return yiSkillMappingItem.Key;
        }

        // 如果没有找到匹配的项,记录错误并返回默认按键
        Debug.Log("该技能没有在技能映射字典");
        return KeyCode.Space;
    }
}

易大师输入管理器类

// 易大师输入管理器类,用于处理技能输入和解析
public class YiInputManager : MonoBehaviorSingleton<YiInputManager>
{
    // 存储易大师的输入信息列表
    private List<YiInputInfo> yiInputInfoList = new List<YiInputInfo>();

    // 用于临时存储连招输入信息的按键列表
    private List<KeyCode> comboInputInfoKeyCodeList = new List<KeyCode>();

    // 输入管理器的活动状态标志
    private bool isActive;

    // 易大师上下文,用于技能解析和执行
    private YiContext yiContext;

    // 连续输入的时间阈值
    private float CONTINUOUS_INPUT_THRESHOLD = 0.2f;

    // 上次输入时间
    private float lastInputTime;

    // 标志是否正在连续输入
    private bool isInputContinuous;

    // 向输入列表添加输入信息
    public void AddYiInputInfo(YiInputInfo yiInputInfo)
    {
        yiInputInfoList.Add(yiInputInfo);
    }

    // 设置输入管理器的活动状态和相关上下文
    public void SetActive(bool active, YiContext context)
    {
        this.isActive = active;
        this.yiContext = context;
    }

    // 解析输入信息为技能表达式
    public ISkillExpression ParseYiInputInfoToSkillExpression(YiInputInfo yiInputInfo)
    {
        // 检查输入信息的类型是否为单个按键输入
        if (yiInputInfo is YiSingleInputInfo)
        {
            // 将输入信息转换为单个按键输入对象
            YiSingleInputInfo yiSingleInputInfo = yiInputInfo as YiSingleInputInfo;

            // 检查按键是否在技能映射字典中
            if (yiContext.YiSkillMappingDictionary.ContainsKey(yiSingleInputInfo.keyCode))
            {
                // 根据按键查找对应的技能表达式类型,并创建技能表达式实例
                return (ISkillExpression)Activator.CreateInstance(
                    yiContext.YiSkillMappingDictionary[yiSingleInputInfo.keyCode]);
            }
        }
        // 如果输入信息的类型为连招输入
        else if (yiInputInfo is YiComboInputInfo)
        {
            // 将输入信息转换为连招输入对象
            YiComboInputInfo yiComboInputInfo = yiInputInfo as YiComboInputInfo;

            List<ISkillExpression> comboSkillExpressionList = new List<ISkillExpression>();

            // 遍历连招输入中的按键列表
            foreach (var keyCode in yiComboInputInfo.keyCodeList)
            {
                // 检查按键是否在技能映射字典中
                if (yiContext.YiSkillMappingDictionary.ContainsKey(keyCode))
                {
                    // 根据按键查找对应的技能表达式类型,并创建技能表达式实例,添加到连招技能表达式列表
                    ISkillExpression skillExpression =
                        (ISkillExpression)Activator.CreateInstance(yiContext.YiSkillMappingDictionary[keyCode]);
                    comboSkillExpressionList.Add(skillExpression);
                }
            }

            // 创建并返回一个连招技能表达式
            return new ComboExpression(comboSkillExpressionList);
        }

        // 如果无法识别输入信息的类型或按键无法匹配到技能表达式,记录错误并返回空
        Debug.Log("无法转换为技能表达式");
        return null;
    }


    // 检查输入信息
    public void CheckInputInfo()
    {
        float currentTime = Time.time;

        // 如果距离上次输入时间超过连续输入的时间阈值
        if (currentTime - lastInputTime > CONTINUOUS_INPUT_THRESHOLD)
        {
            // 如果连招输入列表不为空
            if (comboInputInfoKeyCodeList.Count != 0)
            {
                // 如果连招输入列表只包含一个按键
                if (comboInputInfoKeyCodeList.Count == 1)
                {
                    // 创建一个单按键输入信息并添加到输入信息列表中
                    List<KeyCode> inputInfoKeyCodeList = comboInputInfoKeyCodeList.ToList();
                    AddYiInputInfo(new YiSingleInputInfo(inputInfoKeyCodeList[0]));
                    comboInputInfoKeyCodeList.Clear();
                    Debug.Log("非连续输入");
                }
                // 如果连招输入列表包含多个按键
                else if (comboInputInfoKeyCodeList.Count > 1)
                {
                    // 创建一个连招输入信息并添加到输入信息列表中
                    List<KeyCode> inputInfoKeyCodeList = comboInputInfoKeyCodeList.ToList();
                    AddYiInputInfo(new YiComboInputInfo(inputInfoKeyCodeList));
                    comboInputInfoKeyCodeList.Clear();
                    Debug.Log("连续输入");
                }
            }
        }

        // 如果有任何按键被按下
        if (UnityEngine.Input.anyKeyDown)
        {
            // 遍历易大师技能映射字典中的每个键值对
            foreach (var yiSKillMappingItem in yiContext.YiSkillMappingDictionary)
            {
                // 检查当前帧是否按下了映射字典中的某个技能按键
                if (UnityEngine.Input.GetKeyDown(yiSKillMappingItem.Key))
                {
                    currentTime = Time.time;

                    // 判断当前时间与上次输入时间的差值是否小于连续输入的阈值
                    if (currentTime - lastInputTime <= CONTINUOUS_INPUT_THRESHOLD)
                    {
                        // 表示此次输入为连续输入
                        isInputContinuous = true;
                    }
                    else
                    {
                        // 重置连续输入标志,表示此次输入为非连续输入
                        isInputContinuous = false;
                    }

                    // 处理连续输入
                    if (isInputContinuous)
                    {
                        Debug.Log("连续输入");
                        comboInputInfoKeyCodeList.Add(yiSKillMappingItem.Key);
                    }
                    // 处理非连续输入
                    else
                    {
                        if (comboInputInfoKeyCodeList.Count != 0)
                        {
                            // 如果连招输入列表只包含一个按键
                            if (comboInputInfoKeyCodeList.Count == 1)
                            {
                                Debug.Log("非连续输入");

                                // 创建单按键输入信息并添加到输入信息列表中
                                List<KeyCode> inputInfoKeyCodeList = comboInputInfoKeyCodeList.ToList();
                                AddYiInputInfo(new YiSingleInputInfo(inputInfoKeyCodeList[0]));
                                comboInputInfoKeyCodeList.Clear();
                                comboInputInfoKeyCodeList.Add(yiSKillMappingItem.Key);
                            }
                            // 如果连招输入列表包含多个按键
                            else if (comboInputInfoKeyCodeList.Count > 1)
                            {
                                Debug.Log("连续输入");

                                // 创建连招输入信息并添加到输入信息列表中
                                List<KeyCode> inputInfoKeyCodeList = comboInputInfoKeyCodeList.ToList();
                                AddYiInputInfo(new YiComboInputInfo(inputInfoKeyCodeList));
                                comboInputInfoKeyCodeList.Clear();
                                comboInputInfoKeyCodeList.Add(yiSKillMappingItem.Key);
                            }
                        }
                        else
                        {
                            // 如果连招输入列表为空,直接添加当前按键
                            comboInputInfoKeyCodeList.Add(yiSKillMappingItem.Key);
                        }
                    }

                    // 更新上次输入时间
                    lastInputTime = currentTime;
                }
            }
        }
    }


    private void Update()
    {
        if (!isActive) return;

        CheckInputInfo();

        if (yiInputInfoList.Count > 0 && yiContext.IsYiIdle())
        {
            // 执行下一个技能表达式并从输入列表中移除
            yiContext.ExecuteSkill(ParseYiInputInfoToSkillExpression(yiInputInfoList[0]));
            yiInputInfoList.Remove(yiInputInfoList[0]);
        }
    }
}

客户端

public class TestInterpreterPattern:MonoBehaviour
{
    void Start()
    {
        // 创建无极剑圣易大师
        YiContext yiContext = new YiContext();

        YiInputManager.GetInstance().SetActive(true,yiContext);
        
    }
}

运行结果



转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏