优秀的编程知识分享平台

网站首页 > 技术文章 正文

面向对象的可复用设计模式之解释器模式(16/24)

nanyue 2024-07-29 01:11:52 技术文章 6 ℃

给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。


AbstractExpression:声明一个抽象的解释操作,这个接口被抽象语法树中所有的节点所共享;

TernimalExpression:一个句子中的每个终结符需要该类的一个实例,它实现与文法中的终结符相关联的解释操作;

NonternimalExpression

对于文法中的每一条规则都需要一个NonternimalExpression类;

为文法中的的每个符号都维护一个AbstractExpression类型的实例变量;

为文法中的非终结符实现解释操作,在实现时,一般要递归地调用表示文法符号的那些对象的解释操作;

Context:包含解释器之外的一些全局信息;

Client:构建一个需要进行解释操作的文法句子,然后调用解释操作进行解释。

实际进行解释时,按照以下时序进行的:

Client构建一个句子,它是NonterminalExpression和TerminalExpression的实例的一个抽象语法树,然后初始化上下文并调用解释操作;

每一非终结符表达式节点定义相应子表达式的解释操作。而各终结符表达式的解释操作构成了递归的基础;

每一节点的解释操作用作用上下文来存储和访问解释器的状态。

#include <iostream>
#include <vector>
using namespace std;

#define MAX_SIZE 256
#define SAFE_DELETE(p) if (p) { delete p; p = NULL; }

const wchar_t *const DOWN = L"down";
const wchar_t *const UP = L"up";
const wchar_t *const LEFT = L"left";
const wchar_t *const RIGHT = L"right";

const wchar_t *const MOVE = L"move";
const wchar_t *const WALK = L"walk";

class AbstractNode
{
public:
     virtual wchar_t *Interpret() = 0;
};

class AndNode : public AbstractNode
{
public:
     AndNode(AbstractNode *left, AbstractNode *right) : m_pLeft(left), m_pRight(right){}

     wchar_t *Interpret()
     {
          wchar_t *pResult = new wchar_t[MAX_SIZE];
          memset(pResult, 0, MAX_SIZE * sizeof(wchar_t));

          wchar_t *pLeft = m_pLeft->Interpret();
          wchar_t *pRight = m_pRight->Interpret();
          wcscat_s(pResult, MAX_SIZE, pLeft);
          wcscat_s(pResult, MAX_SIZE, pRight);

          SAFE_DELETE(pLeft);
          SAFE_DELETE(m_pRight);

          return pResult;
     }

private:
     AbstractNode *m_pLeft;
     AbstractNode *m_pRight;
};

class SentenceNode : public AbstractNode
{
public:
     SentenceNode(AbstractNode *direction, AbstractNode *action, AbstractNode *distance) :
          m_pDirection(direction), m_pAction(action), m_pDistance(distance){}

     wchar_t *Interpret()
     {
          wchar_t *pResult = new wchar_t[MAX_SIZE];
          memset(pResult, 0, MAX_SIZE * sizeof(wchar_t));

          wchar_t *pDirection = m_pDirection->Interpret();
          wchar_t *pAction = m_pAction->Interpret();
          wchar_t *pDistance = m_pDistance->Interpret();
          wcscat_s(pResult, MAX_SIZE, pDirection);
          wcscat_s(pResult, MAX_SIZE, pAction);
          wcscat_s(pResult, MAX_SIZE, pDistance);

          SAFE_DELETE(pDirection);
          SAFE_DELETE(pAction);
          SAFE_DELETE(pDistance);

          return pResult;
     }

private:
     AbstractNode *m_pDirection;
     AbstractNode *m_pAction;
     AbstractNode *m_pDistance;
};

class DirectionNode : public AbstractNode
{
public:
     DirectionNode(wchar_t *direction) : m_pDirection(direction){}

     wchar_t *Interpret()
     {
          wchar_t *pResult = new wchar_t[MAX_SIZE];
          memset(pResult, 0, MAX_SIZE * sizeof(wchar_t));

          if (!_wcsicmp(m_pDirection, DOWN))
          {
               wcscat_s(pResult, MAX_SIZE, L"向下");
          }
          else if (!_wcsicmp(m_pDirection, UP))
          {
               wcscat_s(pResult, MAX_SIZE, L"向上");
          }
          else if (!_wcsicmp(m_pDirection, LEFT))
          {
               wcscat_s(pResult, MAX_SIZE, L"向左");
          }
          else if (!_wcsicmp(m_pDirection, RIGHT))
          {
               wcscat_s(pResult, MAX_SIZE, L"向右");
          }
          else
          {
               wcscat_s(pResult, MAX_SIZE, L"无效指令");
          }

          SAFE_DELETE(m_pDirection);
          return pResult;
     }

private:
     wchar_t *m_pDirection;
};

class ActionNode : public AbstractNode
{
public:
     ActionNode(wchar_t *action) : m_pAction(action){}

     wchar_t *Interpret()
     {
          wchar_t *pResult = new wchar_t[MAX_SIZE];
          memset(pResult, 0, MAX_SIZE * sizeof(wchar_t));

          if (!_wcsicmp(m_pAction, MOVE))
          {
               wcscat_s(pResult, MAX_SIZE, L"移动");
          }
          else if (!_wcsicmp(m_pAction, WALK))
          {
               wcscat_s(pResult, MAX_SIZE, L"走动");
          }
          else
          {
               wcscat_s(pResult, MAX_SIZE, L"无效指令");
          }

          SAFE_DELETE(m_pAction);
          return pResult;
     }

private:
     wchar_t *m_pAction;
};

class DistanceNode : public AbstractNode
{
public:
     DistanceNode(wchar_t *distance) : m_pDistance(distance){}

     wchar_t *Interpret()
     {
          wchar_t *pResult = new wchar_t[MAX_SIZE];
          memset(pResult, 0, MAX_SIZE * sizeof(wchar_t));

          wcscat_s(pResult, MAX_SIZE, m_pDistance);

          SAFE_DELETE(m_pDistance);
          return pResult;
     }

private:
     wchar_t *m_pDistance;
};

class InstructionHandler
{
public:
     InstructionHandler(wchar_t *instruction) : m_pInstruction(instruction), m_pTree(NULL){}

     void Handle();
     void Output();

private:
     void SplitInstruction(wchar_t **&instruction, int &size);

     wchar_t *m_pInstruction;
     AbstractNode *m_pTree;
};

void InstructionHandler::Handle()
{
     AbstractNode *pLeft = NULL;
     AbstractNode *pRight = NULL;
     AbstractNode *pDirection = NULL;
     AbstractNode *pAction = NULL;
     AbstractNode *pDistance = NULL;

     vector<AbstractNode *> node; // Store the instruction expression

     // Split the instruction by " "
     wchar_t **InstructionArray = NULL;
     int size;
     SplitInstruction(InstructionArray, size);
     for (int i = 0; i < size; ++i)
     {
          if (!_wcsicmp(InstructionArray[i], L"and")) 
                   // The instruction is composited by two expressions
          {
               wchar_t *pDirectionStr = InstructionArray[++i];
               pDirection = new DirectionNode(pDirectionStr);

               wchar_t *pActionStr = InstructionArray[++i];
               pAction = new ActionNode(pActionStr);

               wchar_t *pDistanceStr = InstructionArray[++i];
               pDistance = new DistanceNode(pDistanceStr);

               pRight = new SentenceNode(pDirection, pAction, pDistance);
               node.push_back(new AndNode(pLeft, pRight));
          }
          else
          {
               wchar_t *pDirectionStr = InstructionArray[i];
               pDirection = new DirectionNode(pDirectionStr);

               wchar_t *pActionStr = InstructionArray[++i];
               pAction = new ActionNode(pActionStr);

               wchar_t *pDistanceStr = InstructionArray[++i];
               pDistance = new DistanceNode(pDistanceStr);

               pLeft = new SentenceNode(pDirection, pAction, pDistance);
               node.push_back(pLeft);
          }
     }

     m_pTree = node[node.size() - 1];
}

void InstructionHandler::Output()
{
     wchar_t *pResult = m_pTree->Interpret();

     setlocale(LC_ALL,"");
     wprintf_s(L"%s\n", pResult);

     SAFE_DELETE(pResult);
}

void InstructionHandler::SplitInstruction(wchar_t **&instruction, int &size)
{
     instruction = new wchar_t*[10];
     memset(instruction, 0, 10 * sizeof( wchar_t*));

     for (int i = 0; i < 10; ++i)
     {
          instruction[i] = new wchar_t[10];
          memset(instruction[i], 0, 10 * sizeof(wchar_t));
     }

     size = 0;
     int n = 0;
     while (*m_pInstruction != L'\0')
     {
          if (*m_pInstruction == L' ')
          {
               size++;
               m_pInstruction++;
               n = 0;
               continue;
          }

          instruction[size][n++] = *m_pInstruction++;
     }
     size++;
}

int main()
{
     wchar_t *pInstructionStr = L"up move 5 and down walk 10";

     InstructionHandler *pInstructionHandler = new InstructionHandler(pInstructionStr);
     pInstructionHandler->Handle();
     pInstructionHandler->Output();

     SAFE_DELETE(pInstructionHandler);
}

我们这里用代码来实现上面的游戏,只不过不是控制小狗在屏幕上移动了,而是将对应的控制指令翻译成汉语进行表示,这和翻译成控制小狗移动的指令的原理是一样的。比如现在有指令:down run 10;那么,经过解释器模式得到的结果为:向下跑动10。

在上面的代码中,我没有用到Context类,一般Context类作为环境上下文类,用于存储解释器之外的一些全局信息,它通常作为参数被传递到所有表达式的解释方法interpret中,可以在Context对象中存储和访问表达式解释器的状态,向表达式解释器提供一些全局的、公共的数据,此外还可以在Context中增加一些所有表达式解释器都共有的功能,减轻解释器的职责。而我们在代码中定义的一些常量,完全可以放入到Context类中,作为上下文的全局数据。


1 解释器模式的使用场景

1.1 当有一个语言需要解释执行,并且你可将该语言中的句子表示为一个抽象语法树,可以使用解释器模式。而当存在以下情况时该模式效果最好

1.2 该文法的类层次结构变得庞大而无法管理。此时语法分析程序生成器这样的工具是最好的选择。他们无需构建抽象语法树即可解释表达式,这样可以节省空间而且还可能节省时间。

1.3 效率不是一个关键问题,最高效的解释器通常不是通过直接解释语法分析树实现的,而是首先将他们装换成另一种形式,例如,正则表达式通常被装换成状态机,即使在这种情况下,转换器仍可用解释器模式实现,该模式仍是有用的


2 解释器模式的优缺点

2.1 优点

2.1.1 可以很容易地改变和扩展方法, 因为该模式使用类来表示方法规则, 你可以使用继承来改变或扩展该方法。

2.1.2 也比较容易实现方法, 因为定义抽象语法树总各个节点的类的实现大体类似, 这些类都易于直接编写。

2.1.3 解释器模式就是将一句话,转变为实际的命令程序执行而已。 而不用解释器模式本身也可以分析, 但通过继承抽象表达式的方式, 由于依赖转置原则, 使得文法的扩展和维护都带来的方便。

2.2 缺点

2.2.1 对于复杂文法难以维护

在解释器模式中,每一条规则至少需要定义一个类,因此如果一个语言包含太多文法规则,类的个数将会急剧增加,导致系统难以管理和维护,此时可以考虑使用语法分析程序等方式来取代解释器模式;

2.2.2 执行效率低

由于在解释器模式中使用了大量的循环和递归调用,因此在解释较为复杂的句子时其速度很慢,而且代码的调试过程也很麻烦。

3 总结

解释器模式在实际的系统开发中使用的非常少,因为它会引起效率、性能以及维护方面的问题,并且难度较大,一般在一些大中型的框架型项目中能够找到它的身影。而现在又有很多的开源库提供了对实际需要的支持,所以,我们在实际开发中没有必要再去重复造轮子,能够理解了解释器模式就好了。

-End-

Tags:

最近发表
标签列表