学习设计模式《二十》——解释器模式
一、基础概念
解释器模式的本质是【分离实现,解释执行】。
解释器模式通过一个解释器对象处理一个语法规则的方式,把复杂的功能分离开;然后选择需要被执行的功能,并把这些功能组合成为需要被解释执行的抽象语法树;再按照抽象语法树来解释执行,实现相应的功能。认识这个本质对于识别和变形使用解释器模式是很有作用的。从表面上看,解释器模式关注的是我们平时不太用到的自定义语法的处理;但从实质上看,解释器模式的思路仍然是分离、封装、简化,和很多模式是一样的。
(比如:可以使用解释器模式模拟状态模式的功能。如果把解释器模式要处理的语法简化到只有一个状态的标记,把解释器看成是对状态的处理对象。对同一个表示状态的语法,可以有很多不同的解释器,也就是有很多不同的处理状态对象,然后在创建抽象语法树的时候,简化成根据状态的标记来创建相应的解释器,不用再构建树。这样简化下来,是不是可以用解释器模拟出状态模式的功能呢?)
解释器模式的定义:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。简单的说就是【语法规则】。
序号 | 认识解释器模式 | 说明 |
1 | 解释器模式的功能 | 使用解释器对象来表示和处理相应的语法规则,一般一个解释器处理一条语法规则。理论上来说,只要能用解释器对象把符合语法的表达式表示出来,而且能够构成抽象的语法树,那都可以使用解释器模式来处理。 |
2 | 语法规则和解释器 | 语法规则和解释器之间是有对应关系的,一般一个解释器处理一条语法规则,但是反过来并不成立(一条语法规则是可以有多种解释和处理的,也就是一条语法规则可以对应多个解释器对象)。 |
3 | 上下文的公用性 | 上下文在解释器模式中起着非常重要的作用。由于上下文会被传递到所有的解释器中,因此可以在上下文中存储和访问解释器的状态(比如:前面的解释器可以存储一些数据在上下文中,后面的解释器就可以获取这些值); 另外还可以通过上下文传递一些在解释器外部,但是解释器需要的数据,也可以是一些全局的、公共的数据。 上下文还有一个功能,就是可以提供所有解释器对象的公共功能,类似于对象组合,而不是使用继承来获取公共功能,在每个解释器对象中都可以调用。 |
4 | 谁来构建抽象语法树 | 自己在客户端手工地构建语法树是很麻烦的,但是在解释器模式中,并没有涉及这部分功能,只是负责对构建好的抽象语法树进行解释处理【可以提供解析器来实现把表达式转换为抽象语法树】。 还有一个问题就是一条语法规则是可以对应多个解释器对象的(也就是说同一个元素,是可以转换成多个解释器对象的,这也意味着同一个表达式,是可以构成不同的抽象语法树的,这也造成了构建抽象语法树变得很困难,且工作量非常大)。 |
5 | 谁负责解释操作 | 只要定义好抽象语法树,肯定是解释器来负责解释执行。虽然有不同的语法规则,但是解释器不负责选择究竟用哪一个解释器对象来解释执行语法规则,选择解释器的功能在构建抽象语法树的时候就完成了。 |
序号 | 解释器模式的优点 | 解释器模式的缺点 |
1 | 易于实现语法 在解释器模式中,一条语法规则用一个解释器对象来解释执行。对于解释器的实现来讲,功能就变得比较简单(只需要考虑一条语法规则的实现就可以了,其他的都不用管)。 | 不适合复杂的语法 (如果语法特别负责,构建解释器模式需要的抽象语法树的工作是非常艰巨的,再加上有可能会需要构建多个抽象语法树。所以解释器模式不太适合于复杂的语法,对于复杂的语法,使用语法分析程序或编译器生成器可能会更好一些)。 |
2 | 易于扩展新的语法 由于采用一个解释器对象负责一条语法规则的方式,使得扩展新的语法非常容易。扩展了新的语法,只需要创建相应的解释器对象,在创建抽象语法树的时候使用这个新的解释器对象就可以了。 |
何时选用解释器模式?
1、当有一个语言需要解释执行,并且可以将该语言中的句子表示为一个抽象语法树的时候,可以考虑使用解释器模式。
2、在使用解释器模式的时候,还需考虑两个内容:
《1》语法相对应该比较简单,太复杂的语法不适合使用解释器模式。
《2》效率要求不是很高,对效率要求很高的情况下,不适合使用解释器模式。
二、解释器模式示例
2.1、不使用模式的示例
业务需求:每个实际的应用系统都有与应用自身相关的配置文件,这个配置文件是由开发人员根据需要自定义的,系统运行时会根据配置的数据进行相应的功能处理【如何能够灵活地读取配置文件的内容】。
2.1.1、XML文件
<?xml version="1.0" encoding="utf-8"?>
<root><jdbc><driver-class>驱动类</driver-class><url>连接数据库的URL</url><user>连接数据库的用户名</user><password>连接数据库的密码</password></jdbc><application-xml>缺省读取的其他配置</application-xml>
</root>
2.1.2、 读取这个XML文件
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Metadata;
using System.Text;
using System.Threading.Tasks;
using System.Xml;namespace InterpreterPattern.NoPattern
{internal class ReadAppXml{//读取配置文件内容public void Read(string filePathName,string rootName){if (string.IsNullOrEmpty(filePathName) || string.IsNullOrEmpty(rootName)) return;XmlDocument xmlDoc= new XmlDocument();xmlDoc.Load(filePathName);//获取根节点XmlNode rootNode = xmlDoc.SelectSingleNode(rootName);//得到根节点下的节点集合XmlNodeList xmlNodeList = rootNode.ChildNodes;//获取根节点下的所有子节点foreach (XmlNode xmlNode in xmlNodeList){switch (xmlNode.Name){case "jdbc":foreach (XmlNode xmlNode2 in xmlNode.ChildNodes){Console.WriteLine($"{xmlNode2.Name}={xmlNode2.InnerText}");}break;case "application-xml":Console.WriteLine($"{xmlNode.Name}={xmlNode.InnerText}");break;default:break;}}}}//Class_end
}
2.1.3、客户端测试
namespace InterpreterPattern
{internal class Program{static void Main(string[] args){TestReadAppXml();Console.ReadLine();}/// <summary>/// 不使用模式示例/// </summary>private static void TestReadAppXml(){Console.WriteLine("------不使用模式示例------");string xmlFile = $"{AppContext.BaseDirectory}\\NoPattern\\XMLFile1.xml";NoPattern.ReadAppXml readAppXml = new NoPattern.ReadAppXml();readAppXml.Read(xmlFile,"root");}}//Class_end
}
2.1.4、运行结果
已经实现了要求的功能解析xml文件的内容,但是有一个问题:如果配置文件的结构需要变动,就需要将代码内容进行针对性的修改 (如修改了配置文件的结构,那么读取配置文件的程序就需要做出相应的调整;用来封装配置文件数据的数据对象也需要相应的修改;外部使用配置文件的地方,获取数据的地方也会相应的变动)。
2.2、使用解释器模式的示例1——读取单个元素或属性的值
有没有办法解决(当xml文件的结构发生改变后,能够很方便地获取相应元素或者属性的值,而不用再去修改解析xml的程序)这个问题呢?
应用解释器模式解决问题的思路:(把解析部分的代码写成公共的,而且还是要通用的,能够满足各种xml取值的需求【如:获取单个元素的值、获取多个元素的值、获取单个元素的属性值、获取多个相同名称元素属性的值等】)。
要写成通用的代码,则需要解决【如何组织这些通用的代码?】【如何调用这些通用的代码?】【以何种方式来告诉这些通用代码客户端的需要?】可以使用解释器模式来解决这些问题:
1、解析器:指的是把描述客户端调用要求的表达式,经过解析,形成一个抽象语法树的程序,不是值xml解析器。
2、解释器:指的是抽象语法树,并执行每个节点对应的功能的程序。
要解决通用解析XML文件的问题:
1、首先需要设计一个简单的表达式语言,在客户端调用解析程序的时候,传入这个表达式语言描述的一个表达式,然后把这个表达式通过解析器的解析,形成一个抽象的语法树。
2、解析完成后,自动调用解释器来解释抽象语法树,并执行每个节点所对应的功能,从而完成通用xml的解析。
这样一来,每次当xml结构发生了更改,在客户端调用的时候,只用传入不同的表达式即可,整个解析xml过程的代码都不需要再修改了。
2.2.1、为表达式设计简单的文法
要使用解释器模式,一个重要的前提是要定义一套语法规则(也叫做文法),不管这套文法的规则是简单还是复杂,必须有这些规则;因为解释器模式就是按照这些规则来进行解析并执行相应功能的。【为了通用,使用root表示根元素,a、b、c、d等来代表元素,一个简单的xml如下】
<?xml version="1.0" encoding="utf-8"?>
<root id="rootId"><a><b><c name="testC">123456</c><d id="1">d1</d><d id="2">d2</d><d id="3">d3</d><d id="4">d4</d></b></a>
</root>
约定表达式的文法如下:
1、获取单个元素的值:从根元素开始,一直到想要获取值的元素,元素中间用“/”分隔,根元素前不加“/”(比如:表达式"root/a/b/c"就表示获取根元素下、a元素下、b元素下、c元素的值)。
2、获取单个元素属性的值:要获取值的属性一定是表达式的最后一个元素的属性,在最后一个元素后面添加“.”然后再加上属性的名称(比如:表达式"root/a/b/c.name"就表示获取根元素下、a元素下、b元素下、c元素的name属性值)。
3、获取相同元素名称的值,当然是多个:要获取值的元素一定是表达式的最后一个元素,在最后一个元素后面添加“$”(比如:表达式"root/a/b/d$"就表示获取根元素下、a元素下、b元素下、d元素的值的集合)。
4、获取相同元素名称的属性值,当然是多个:要获取属性值的元素一定是表达式的最后一个元素,在最后一个元素后面添加“$”,然后在后面添加“.”然后再加上属性的名称,在属性名称后面也添加上“$”(比如:表达式"root/a/b/d$.id$"就表示获取根元素下、a元素下、b元素下、d元素的id属性的值的集合)。
对于抽象的语法树这个树状结构,很明显可以使用组合模式来构建。解释器模式把需要解释的对象分为了两大类:一类是节点元素(可以包含其他元素的组合元素,比如:非终结符元素,对应成为组合模式的Composite);另一类是终结符元素(相当于组合模式的叶子对象);解释整个抽象语法树的过程,也就是执行相应对象的功能的过程。
比如这个xml对应的抽象语法树如下:
2.2.2、定义抽象的解释器
抽象解释器是用来约束所有被解释的语法对象(也就是节点元素和终结符元素都要实现的功能)。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace InterpreterPattern.InterpreterDemoOne
{/// <summary>/// 用于处理自定义xml取值表达式的【抽象解释器】/// </summary>abstract class ReadXmlExpression{/// <summary>/// 解释表达式/// </summary>public abstract string[] Interpret(Context c);}//Class_end
}
2.2.3、定义上下文
上下文是用来封装解释器需要的一些全局数据,也可以在其中封装一些解释器的公共功能,相当于各个解释器的公共对象。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;namespace InterpreterPattern.InterpreterDemoOne
{/// <summary>/// 上下文【用来包含解释器需要的一些全局信息】/// </summary>internal class Context{//上一个被处理的元素private XElement preEle = null;//Dom解析xml的Document对象private XDocument document = null;public XElement PreEle { get => preEle; set => preEle = value; }public XDocument Document { get => document;}/// <summary>/// 构造方法/// </summary>public Context(string filePathName) {if (string.IsNullOrEmpty(filePathName)){return;}this.document=XmlUtil.GetXDocument(filePathName);}/// <summary>/// 重新初始化上下文/// </summary>public void ReInit(){PreEle = null;}/// <summary>/// 各个Expression公共使用的方法(根据父元素和当前元素名称获取到当前的元素)/// </summary>/// <param name="parentElement">父元素</param>/// <param name="curElementName">当前元素名称</param>/// <returns>找到的当前元素</returns>public XElement GetCurrentEle(XElement parentElement,string curElementName){if (parentElement == null || string.IsNullOrEmpty(curElementName)){return null;}IEnumerable<XElement> elements = parentElement.Elements();foreach (XElement element in elements){if (element is XElement){if (element.Name.LocalName.Equals(curElementName)){Console.WriteLine($"当前查询到的元素是【{element.Name}】");return element;}}}return null;}}//Class_end
}
在上下文中使用了XmlUtil来获取Document对象,就是Dom解析xml,获取相应的Document对象:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;namespace InterpreterPattern.InterpreterDemoOne
{/// <summary>/// xml的工具类/// </summary>internal class XmlUtil{/// <summary>/// 获取到xml文件的XDocument/// </summary>/// <param name="filePathName">xml路径和文件(如:D:\\testfile\\xmlfile.xml)</param>/// <returns></returns>public static XDocument GetXDocument(string filePathName){if (string.IsNullOrEmpty(filePathName)) return null;try{XDocument xDoc = XDocument.Load(filePathName);return xDoc;}catch (Exception ex){throw ex;}}}//Class_end
}
2.2.4、定义节点元素对应的解释器
这个元素解释器处理的就是将自己找到,作为下一个元素的父元素就可以了(相当于组合模式中的Composite对象,因此需要对子元素进行维护)。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;namespace InterpreterPattern.InterpreterDemoOne
{/// <summary>/// 元素作为非终结符对应的解释器,解释并执行中间元素/// </summary>internal class ElementExpression:ReadXmlExpression{//用来记录组合的ReadXmlExpression元素private List<ReadXmlExpression> readXmlExpressionList=new List<ReadXmlExpression>();//元素名称private string elementName = string.Empty;public ElementExpression(string elementName){this.elementName = elementName;}public bool AddElement(ReadXmlExpression readXmlExpression){readXmlExpressionList.Add(readXmlExpression);return true;}public bool RemoveElement(ReadXmlExpression readXmlExpression){readXmlExpressionList.Remove(readXmlExpression);return true;}public override string[] Interpret(Context c){/*1-获取上下文的当前元素作为父级元素*///查找到当前元素名称所对应的xml元素,并设置回到上下文中XElement parentElement = c.PreEle;if (parentElement == null){//说明现在获取的是根元素c.PreEle = c.Document.Root;}else{//根据父级元素和要查找的元素名称来获取当前的元素XElement currentElement = c.GetCurrentEle(parentElement,elementName);//把当前获取到的元素放到上下文中c.PreEle = currentElement;}//循环调用子元素的Interparet方法string[] ss = null;foreach (var item in readXmlExpressionList){ss = item.Interpret(c);}return ss;}}//Class_end
}
2.2.5、定义终结符对应的解释器
对于单个元素的处理,终结符有两种:一个是元素终结(就是获取元素的值),另一个是属性终结(就是获取属性的值)。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;namespace InterpreterPattern.InterpreterDemoOne
{/// <summary>/// 元素作为终结符对应的解释器/// </summary>internal class ElementTerminalExpression:ReadXmlExpression{//元素的名字private string elementName=string.Empty;public ElementTerminalExpression(string elementName){this.elementName = elementName;}public override string[] Interpret(Context c){//先取出上下文中的当前元素作为父级元素XElement parentElement = c.PreEle;//查找到当前元素名称对应的xml元素XElement element = null;if (parentElement == null){//说明现在获取的是根元素element = c.Document.Root;c.PreEle = element;}else{//根据父级元素和要查找的元素名称来获取当前的元素element = c.GetCurrentEle(parentElement,elementName);//把当前获取到的元素放到上下文中c.PreEle = element;}//然后根据需要去获取这个元素的值string[] ss=new string[1];ss[0] = element.FirstNode.ToString();return ss;}}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;namespace InterpreterPattern.InterpreterDemoOne
{/// <summary>/// 属性作为终结符对应的解释器/// </summary>internal class PropertyTerminalExpression : ReadXmlExpression{//属性private string propertyName=string.Empty;public PropertyTerminalExpression(string propertyName){this.propertyName = propertyName;}public override string[] Interpret(Context c){//直接获取最后的元素属性值string[] ss= new string[1];XElement element = c.PreEle;IEnumerable<XAttribute> xAttributeList = element.Attributes();foreach (XAttribute xAttribute in xAttributeList){if (xAttribute.Name.LocalName.Equals(propertyName)){ss[0] = xAttribute.Value;}}return ss;}}//Class_end
}
2.2.6、客户端测试——获取单元素值
定义好了各个解释器的实现,就可以在客户端测试这些解释器对象的功能了【使用解释器的客户端工作比较多,最主要的就是组装抽象的语法树】(这里使用解释器获取单个元素的值)。
namespace InterpreterPattern
{internal class Program{static void Main(string[] args){TestInterpreterDemoOne();Console.ReadLine();}/// <summary>/// 测试解释器模式示例一/// </summary>private static void TestInterpreterDemoOne(){Console.WriteLine("------测试解释器模式示例一------");string xmlFile = $"{AppContext.BaseDirectory}\\InterpreterDemoOne\\XMLFile1.xml";//准备上下文InterpreterDemoOne.Context context = new InterpreterDemoOne.Context(xmlFile);/*想要获取上下文元素的值(也就是表达式的值"root/a/b/c")*///1-构建解释器的抽象语法树InterpreterDemoOne.ElementExpression root = new InterpreterDemoOne.ElementExpression("root");InterpreterDemoOne.ElementExpression aElement = new InterpreterDemoOne.ElementExpression("a");InterpreterDemoOne.ElementExpression bElement = new InterpreterDemoOne.ElementExpression("b");InterpreterDemoOne.ElementTerminalExpression cElement = new InterpreterDemoOne.ElementTerminalExpression("c");//2-组合抽象语法树root.AddElement(aElement);aElement.AddElement(bElement);bElement.AddElement(cElement);//3-调用解释器解析string[] ss = root.Interpret(context);Console.WriteLine($"最后这个结束元素c的值是【{ss[0]}】");}}//Class_end
}
2.2.7、运行结果
2.2.6-1、客户端测试——获取单元素属性值
namespace InterpreterPattern
{internal class Program{static void Main(string[] args){TestInterpreterDemoOne2();Console.ReadLine();}/// <summary>/// 测试解释器模式示例一2/// </summary>private static void TestInterpreterDemoOne2(){Console.WriteLine("------测试解释器模式示例一2------");string xmlFile = $"{AppContext.BaseDirectory}\\InterpreterDemoOne\\XMLFile1.xml";//准备上下文InterpreterDemoOne.Context context = new InterpreterDemoOne.Context(xmlFile);/*想要获取上下文元素的c值(也就是表达式的值"root/a/b/c,name")*///1-构建解释器的抽象语法树InterpreterDemoOne.ElementExpression root = new InterpreterDemoOne.ElementExpression("root");InterpreterDemoOne.ElementExpression aElement = new InterpreterDemoOne.ElementExpression("a");InterpreterDemoOne.ElementExpression bElement = new InterpreterDemoOne.ElementExpression("b");InterpreterDemoOne.ElementExpression cElement = new InterpreterDemoOne.ElementExpression("c");InterpreterDemoOne.PropertyTerminalExpression cProperty = new InterpreterDemoOne.PropertyTerminalExpression("name");//2-组合抽象语法树root.AddElement(aElement);aElement.AddElement(bElement);bElement.AddElement(cElement);cElement.AddElement(cProperty);//3-调用解释器解析string[] ss = root.Interpret(context);Console.WriteLine($"最后这个结束元素c的name属性值是【{ss[0]}】");//若要继续使用同一个上下文,连续解析则需要初始化上下文对象(如:要连续的再重新获取一次属性Name值//你可以重新组合对象重新解析,只要是在使用同一个上下文,就需要重新初始化上下文对象)context.ReInit();/*获取d元素的属性*///1-清除c的属性终结符解释器和c元素cElement.RemoveElement(cProperty);bElement.RemoveElement(cElement);//2-获取d元素和属性作为终结符InterpreterDemoOne.ElementTerminalExpression cElementValue = new InterpreterDemoOne.ElementTerminalExpression("c");//3-组合对象bElement.AddElement(cElementValue);ss = root.Interpret(context);Console.WriteLine($"最后这个结束元素d的值是【{ss[0]}】");}}//Class_end
}
2.2.7-2、运行结果
在这个示例中,我们只能看到客户端直接使用解释器对象,来表示客户要从xml中取什么值的语法树,而没有看到如何从语言的表达式转换为这种解释器的表示,这个功能是属于解析器的功能,没有划分在标准的解释器模式中,所以这里就先不演示,在最后的示例3会介绍说明。
2.3、使用解释器模式的示例2——读取多个元素或属性的值
获取多个值和前面获取单个值的实现思路大致相同,只是在取值的时候需要循环整个节点列表,依次取值,而不是只取出第一个来。当然,由于语法发生了变动,所以对应的解释器也发生改变:
1、增加了表示多个元素作为终结符的语法(比如:"root/a/b/d$"中的d$);
2、增加了表示多个元素属性作为终结符的语法(比如:"root/a/b/d$.id$"中的id$);
3、增加了表示多个元素,但不是终结符的语法(比如:"root/a/b/d$.id$"中的d$).
抽象解释器和读取xml的工具类XmlUtil么有任何变化。上下文做了一些修改:
1、把原来记录上一次操作的元素,变为记录上一次操作的多个元素的集合,然后为它提供相应的get\set方法。
2、原来根据父元素和当前元素名称获取当前元素的方法,修改为根据父元素和当前元素名称来获取多个元素。
3、重新初始化上下文方法中,初始化就是记录上一次操作的多个元素这个集合。
2.3.1、具体的上下文
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;namespace InterpreterPattern.InterpreterDemoTwo
{/// <summary>/// 上下文【用来包含解释器需要的一些全局信息】/// </summary>internal class Context{//上一次被处理的多个元素private List<XElement> preElementList = new List<XElement>();//Dom解析xml的Document对象private XDocument document = null;public List<XElement> PreElementList { get=>preElementList; set=>preElementList=value; }public XDocument Document { get=>document; }/// <summary>/// 构造方法/// </summary>public Context(string filePathName){if (string.IsNullOrEmpty(filePathName)){return;}document = XmlUtil.GetXDocument(filePathName);}/// <summary>/// 重新初始化上下文/// </summary>public void ReInit(){preElementList = new List<XElement>();}/// <summary>/// 各个Expression公共使用的方法(根据父元素和当前元素名称获取到当前的元素)/// </summary>/// <param name="parentElement">父元素</param>/// <param name="curElementName">当前元素名称</param>/// <returns>找到的当前元素</returns>public List<XElement> GetCurrentEle(XElement parentElement, string curElementName){List<XElement> xElements = new List<XElement>();if (parentElement == null || string.IsNullOrEmpty(curElementName)){return xElements;}IEnumerable<XElement> tmpElementList = parentElement.Elements();foreach (XElement element in tmpElementList){if (element is XElement){if (element.Name.LocalName.Equals(curElementName)){xElements.Add(element);}}}return xElements;}}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;namespace InterpreterPattern.InterpreterDemoTwo
{/// <summary>/// xml的工具类/// </summary>internal class XmlUtil{/// <summary>/// 获取到xml文件的XDocument/// </summary>/// <param name="filePathName">xml路径和文件(如:D:\\testfile\\xmlfile.xml)</param>/// <returns></returns>public static XDocument GetXDocument(string filePathName){if (string.IsNullOrEmpty(filePathName)) return null;try{XDocument xDoc = XDocument.Load(filePathName);return xDoc;}catch (Exception ex){throw ex;}}}//Class_end
}
2.3.2、单元素对应的解释器
相比以前的单元素处理,主要是现在需要面向多个父元素;由于是单个非终结符的处理,因此在多个父元素下面去查找符合要求的元素,找到一个就停止查找了。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;namespace InterpreterPattern.InterpreterDemoTwo
{/// <summary>/// 单元素作为非终结符对应的解释器,解释并执行中间元素【面向多个父元素】/// </summary>internal class ElementExpression : ReadXmlExpression{//用来记录组合的ReadXmlExpression元素private List<ReadXmlExpression> readXmlExpressionList = new List<ReadXmlExpression>();//元素名称private string elementName = string.Empty;public ElementExpression(string elementName){this.elementName = elementName;}public bool AddElement(ReadXmlExpression readXmlExpression){readXmlExpressionList.Add(readXmlExpression);return true;}public bool RemoveElement(ReadXmlExpression readXmlExpression){readXmlExpressionList.Remove(readXmlExpression);return true;}public override string[] Interpret(Context c){/*1-获取上下文的当前元素作为父级元素*/List<XElement> parentElementList = c.PreElementList;XElement xElement = null;//查找到当前元素名称所对应的xml元素,并设置回到上下文中List<XElement>currentElementList= new List<XElement>();if (parentElementList == null || parentElementList?.Count<=0){//说明现在获取的是根元素xElement = c.Document.Root;parentElementList.Add(xElement);c.PreElementList = parentElementList;}else{//根据父级元素和要查找的元素名称来获取当前的元素foreach (var item in parentElementList){currentElementList.AddRange(c.GetCurrentEle(item,elementName));if (currentElementList!=null && currentElementList?.Count>0){//找到一个就停止查找了break;}}List<XElement> tmpElementList= new List<XElement>();tmpElementList.Add(currentElementList[0]);c.PreElementList = tmpElementList;}//循环调用子元素的Interparet方法string[] ss = null;foreach (var item in readXmlExpressionList){ss = item.Interpret(c);}return ss;}}//Class_end
}
2.3.3、单元素终结符对应的解释器
主要是从多个父元素去获取当前元素,如果当前元素是多个,就取第一个:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;namespace InterpreterPattern.InterpreterDemoTwo
{/// <summary>/// 元素作为终结符对应的解释器【从多个父元素中去获取当前元素,若当前元素是多个,只取第一个】/// </summary>internal class ElementTerminalExpression : ReadXmlExpression{//元素的名字private string elementName = string.Empty;public ElementTerminalExpression(string elementName){this.elementName = elementName;}public override string[] Interpret(Context c){//先取出上下文中的当前元素作为父级元素List<XElement> parentElementList = c.PreElementList;//查找到当前元素名称对应的xml元素XElement element = null;if (parentElementList == null || parentElementList?.Count<=0){//说明现在获取的是根元素element = c.Document.Root;}else{//根据父级元素和要查找的元素名称来获取当前的元素element = c.GetCurrentEle(parentElementList[0], elementName)[0];}//然后根据需要去获取这个元素的值string[] ss = new string[1];ss[0] = element.FirstNode.ToString();return ss;}}//Class_end
}
2.3.4、多元素属性作为终结符的解释器
只要获取到最后的多个元素对象,然后循环这些元素,一个个取出相应的属性值:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;namespace InterpreterPattern.InterpreterDemoTwo
{/// <summary>/// 以多个元素属性作为终结符对应的解释器/// </summary>internal class MutiPropertyTerminalExpression : ReadXmlExpression{//属性名称private string propertyName = string.Empty;public MutiPropertyTerminalExpression(string propertyName){this.propertyName = propertyName;}public override string[] Interpret(Context c){//直接获取最后的多个元素属性值List<XElement> elementList = c.PreElementList;string[] ss = new string[elementList.Count];for (int i = 0; i < ss.Length; i++){ss[i] = elementList[i].Attribute(this.propertyName).Value;}return ss;}}//Class_end
}
2.3.5、多元素终结符对应的解释器
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;namespace InterpreterPattern.InterpreterDemoTwo
{/// <summary>/// 多元素作为终结符对应的解释器/// </summary>internal class MutiElementTerminalExpression : ReadXmlExpression{//元素的名字private string elementName = string.Empty;public MutiElementTerminalExpression(string elementName){this.elementName = elementName;}public override string[] Interpret(Context c){//先取出上下文中的当前元素作为父级元素List<XElement> parentElementList = c.PreElementList;//获取当前的多个元素List<XElement> currentElementList = new List<XElement>();foreach (XElement element in parentElementList){currentElementList.AddRange(c.GetCurrentEle(element,elementName));}//获取这些元素值string[] ss = new string[currentElementList.Count];for (int i = 0; i < ss.Length; i++){ss[i] = currentElementList[i].FirstNode.ToString();}return ss;}}//Class_end
}
2.3.6、多元素对应的解释器
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;namespace InterpreterPattern.InterpreterDemoTwo
{/// <summary>/// 多元素作为非终结符对应的解释器,解释并执行中间元素/// </summary>internal class MutiElementExpression : ReadXmlExpression{//用来记录组合的ReadXmlExpression元素private List<ReadXmlExpression> readXmlExpressionList = new List<ReadXmlExpression>();//元素名称private string elementName = string.Empty;public MutiElementExpression(string elementName){this.elementName = elementName;}public bool AddElement(ReadXmlExpression readXmlExpression){readXmlExpressionList.Add(readXmlExpression);return true;}public bool RemoveElement(ReadXmlExpression readXmlExpression){readXmlExpressionList.Remove(readXmlExpression);return true;}public override string[] Interpret(Context c){/*1-获取上下文的当前元素作为父级元素*/List<XElement> parentElementList = c.PreElementList;//查找到当前元素名称所对应的xml元素,并设置回到上下文中List<XElement>currentElementList= new List<XElement>();foreach (XElement element in parentElementList){currentElementList.AddRange(c.GetCurrentEle(element,elementName));}c.PreElementList = currentElementList;//循环调用子元素的Interparet方法string[] ss = null;foreach (var item in readXmlExpressionList){ss = item.Interpret(c);}return ss;}}//Class_end
}
2.3.7、客户端测试
namespace InterpreterPattern
{internal class Program{static void Main(string[] args){TestInterpreterDemoTwo();Console.ReadLine();}/// <summary>/// 测试解释器模式示例二/// </summary>private static void TestInterpreterDemoTwo(){Console.WriteLine("------测试解释器模式示例二------");string xmlFile = $"{AppContext.BaseDirectory}\\InterpreterDemoTwo\\XMLFile1.xml";//准备上下文InterpreterDemoTwo.Context context = new InterpreterDemoTwo.Context(xmlFile);/*想要获取多个d元素的值(如“root/a/b/d$”)*///首先需要构建解释器的抽象语法树InterpreterDemoTwo.ElementExpression root= new InterpreterDemoTwo.ElementExpression("root");InterpreterDemoTwo.ElementExpression aElement= new InterpreterDemoTwo.ElementExpression("a");InterpreterDemoTwo.ElementExpression bElement=new InterpreterDemoTwo.ElementExpression("b");InterpreterDemoTwo.MutiElementTerminalExpression dElement=new InterpreterDemoTwo.MutiElementTerminalExpression("d");//组合语法树root.AddElement(aElement);aElement.AddElement(bElement);bElement.AddElement(dElement);//调用解析string[] ss=root.Interpret(context);foreach (var item in ss){Console.WriteLine($"d的值是【{item}】");}context.ReInit();bElement.RemoveElement(dElement);InterpreterDemoTwo.MutiElementExpression dElement2 = new InterpreterDemoTwo.MutiElementExpression("d");InterpreterDemoTwo.MutiPropertyTerminalExpression dProperty = new InterpreterDemoTwo.MutiPropertyTerminalExpression("id");bElement.AddElement(dElement2);dElement2.AddElement(dProperty);string[] ss2= root.Interpret(context);foreach (var item in ss2){Console.WriteLine($"d的属性值是【{item}】");}}}//Class_end
}
2.3.8、运行结果
2.4、使用解释器模式的示例3
上面是解释器部分的功能,只要构建好了抽象语法树,解释器就能够正确地解释并执行它,该如何得到这个抽象语法树呢?上面的测试都是人工组合好抽象语法树的(若在实际项目中这样做,那么工作量与直接修改解析xml的代码差不多)。
这时就需要解释器出场了,这个程序专门负责把按照语法表达的表达式,解析转换为解释器需要的抽象语法树。当然解析器是和表达式的语法以及解释器对象紧密关联的。
解析器的实现思路:
1、把客户端传递过来的表达式进行分解,分解为一个个的元素,并用一个对应的解析模型来封装这个元素的信息。
2、根据每个元素的信息,转化为相应的解析器对象。
3、按照先后顺序,把这些解析器对象组合起来,就得到抽象语法树了。
2.4.1、封装每个解析元素的属性对象
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace InterpreterPattern.InterpreterDemoThree
{/// <summary>/// 用来封装每一个解析出来的元素对应的属性/// </summary>internal class ParserModel{/// <summary>/// 是否单个值/// </summary>public bool SingleValue { get; set; }/// <summary>/// 是否是属性(不是就是元素)/// </summary>public bool PropertyValue { get; set; }/// <summary>/// 是否是终结符/// </summary>public bool End { get; set; }}//Class_end
}
2.4.2、实现解析器
注意:如下实现的解析器没有考虑多线程并发的情况,如果要用在多线程环境相爱,需要补充相应的处理:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace InterpreterPattern.InterpreterDemoThree
{/// <summary>/// 根据语法来解析表达式【转换为相应的抽象语法树】/// </summary>internal class Parser{//定义几个常量,内部使用private const char BACKLASH = '/';private const char DOT = '.';private const string DOLLAR = "$";//按照分解的先后记录需要解析的元素名称private static List<string>elementList=null;/// <summary>/// 私有化构造器,避免外部无谓的创建对象实例/// </summary>private Parser(){}/// <summary>/// 传入一个字符串表达式(通过解析,组合成为一个抽象的语法树)/// </summary>/// <param name="expr">要取值的字符串表达式</param>/// <returns>对应的抽象语法树</returns>public static ReadXmlExpression Parse(string expr){//先初始化记录需要解析的元素名称集合elementList=new List<string>();//1-分解表达式,得到需要解析的元素名称和该元素对应的解析模型Dictionary<string,ParserModel>dicPath=ParseDicPath(expr);//2-根据节点的属性转换为相应的解释器对象List<ReadXmlExpression> readXmlExpressionList = DicPathToInterpreter(dicPath);//3-组合抽象语法树,一定要按照先后顺序组合,否则对象的包含关系就混乱了ReadXmlExpression readXmlExpression =BuildTree(readXmlExpressionList);return readXmlExpression;}/// <summary>/// 按照从左到右的顺序来分解表达式,得到需要解析的元素名称/// </summary>/// <param name="expr">需要分解的表达式</param>/// <returns>得到需要解析的元素名称,还有该元素对应的解析模型</returns>private static Dictionary<string, ParserModel> ParseDicPath(string expr){//按照/分割字符串string[] strTmp = expr.Split(BACKLASH);//初始化一个字典来存放分解出来的值Dictionary<string,ParserModel> DicPath=new Dictionary<string, ParserModel>();int len=strTmp.Length;for (int i = 0; i < len; i++){if (i < len - 1){//说明这不是最后一个元素//按照现在的语法,属性必然在最后,因此也不是属性SetParsePath(false, strTmp[i], false, DicPath);}else{//到最后int dotIndex = strTmp[i].IndexOf(DOT);if (dotIndex > 0){//说明是要获取属性的值,那就按照"."来分割//前面的就是元素的名称,后面的是属性的名字string elementName = strTmp[i].Substring(0, dotIndex);string propertyName = strTmp[i].Substring(dotIndex + 1);//设置属性前面的元素(不是最后一个,也不是属性)SetParsePath(false, elementName, false, DicPath);//设置属性(按照现在的语法定义,属性只能是最后一个)SetParsePath(true, propertyName, true, DicPath);}else{//说明是取元素的值,而且是最后一个元素的值SetParsePath(true, strTmp[i],false,DicPath);}break;}}return DicPath;}/// <summary>/// 按照分解出来的位置和名称来设置需要解析的元素名称/// </summary>/// <param name="end">是否为最后一个</param>/// <param name="elementName">元素名称</param>/// <param name="propertyValue">是否取属性值</param>/// <param name="DicPath">设置需要解析的元素名称</param>private static void SetParsePath(bool end,string elementName,bool propertyValue,Dictionary<string,ParserModel>DicPath){ParserModel pm = new ParserModel();pm.End = end;//如果带有$符号就说明不是一个值pm.SingleValue = (!(elementName.IndexOf(DOLLAR)>0));pm.PropertyValue = propertyValue;//去掉$elementName = elementName.Replace(DOLLAR,"");DicPath.Add(elementName, pm);elementList.Add(elementName);}/// <summary>/// 把分解出来的元素名称根据对应的解析模型转换为相应的解释器对象/// </summary>/// <param name="dicPath">分解出来的需要解析的元素名称,还有该元素对应的解析模型</param>/// <returns>把每个元素转换为相应的解释器对象后的集合</returns>private static List<ReadXmlExpression> DicPathToInterpreter(Dictionary<string,ParserModel>dicPath){List<ReadXmlExpression> readXmlExpressionList=new List<ReadXmlExpression>();//一定要按照分解的先后顺序来转换成解释器对象foreach (var item in elementList){ParserModel pm = dicPath[item];ReadXmlExpression obj = null;if (!pm.End){if (pm.SingleValue){//不是最后一个,是一个值则转换obj= new ElementExpression(item);}else{//不是最后一个,是多个值则转换obj=new MutiElementExpression(item);}}else{if (pm.PropertyValue){if (pm.SingleValue){//是最后一个,是一个值,取属性的值obj = new PropertyTerminalExpression(item);}else{//是最后一个,是多个值,取属性的值obj = new MutiPropertyTerminalExpression(item);}}else{if (pm.SingleValue){//是最后一个,是一个值,取元素的值obj = new ElementTerminalExpression(item);}else{//是最后一个,是多个值,取元素的值obj = new MutiElementTerminalExpression(item);}}}//把转换后的对象添加到集合中readXmlExpressionList.Add(obj);}return readXmlExpressionList;}/// <summary>/// 构建语法树/// </summary>/// <param name="readXmlExpressionList">根据节点的属性转换为相应的解释器对象</param>/// <returns></returns>private static ReadXmlExpression BuildTree(List<ReadXmlExpression>readXmlExpressionList){//第一个对象(返回去的对象,就是抽象语法树的根)ReadXmlExpression returnReadXmlExpression = null;//定义一个对象ReadXmlExpression parentReadXmlExpression = null;foreach (var item in readXmlExpressionList){if (parentReadXmlExpression == null){//说明是第一个元素parentReadXmlExpression = item;returnReadXmlExpression = item;}else {//把元素添加到上一个对象下面,同时把本对象设置为oldReadXmlExpression,作为下一个对象的父节点if (parentReadXmlExpression is ElementExpression){ElementExpression elementExpression = (ElementExpression)parentReadXmlExpression;elementExpression.AddElement(item);parentReadXmlExpression = item;}else if (parentReadXmlExpression is MutiElementExpression){MutiElementExpression mutiElementExpression = (MutiElementExpression)parentReadXmlExpression;mutiElementExpression.AddElement(item);parentReadXmlExpression = item;}}}return returnReadXmlExpression;}}//Class_end
}
2.4.3、抽象解释器和具体的上下文
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace InterpreterPattern.InterpreterDemoThree
{/// <summary>/// 用于处理自定义xml取值表达式的【抽象解释器】/// </summary>abstract class ReadXmlExpression{/// <summary>/// 解释表达式/// </summary>public abstract string[] Interpret(Context c);}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;namespace InterpreterPattern.InterpreterDemoThree
{/// <summary>/// 上下文【用来包含解释器需要的一些全局信息】/// </summary>internal class Context{//上一次被处理的多个元素private List<XElement> preElementList = new List<XElement>();//Dom解析xml的Document对象private XDocument document = null;public List<XElement> PreElementList { get => preElementList; set => preElementList = value; }public XDocument Document { get => document; }/// <summary>/// 构造方法/// </summary>public Context(string filePathName){if (string.IsNullOrEmpty(filePathName)){return;}document = XmlUtil.GetXDocument(filePathName);}/// <summary>/// 重新初始化上下文/// </summary>public void ReInit(){preElementList = new List<XElement>();}/// <summary>/// 各个Expression公共使用的方法(根据父元素和当前元素名称获取到当前的元素)/// </summary>/// <param name="parentElement">父元素</param>/// <param name="curElementName">当前元素名称</param>/// <returns>找到的当前元素</returns>public List<XElement> GetCurrentEle(XElement parentElement, string curElementName){List<XElement> xElements = new List<XElement>();if (parentElement == null || string.IsNullOrEmpty(curElementName)){return xElements;}IEnumerable<XElement> tmpElementList = parentElement.Elements();foreach (XElement element in tmpElementList){if (element is XElement){if (element.Name.LocalName.Equals(curElementName)){xElements.Add(element);}}}return xElements;}}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;namespace InterpreterPattern.InterpreterDemoThree
{/// <summary>/// xml的工具类/// </summary>internal class XmlUtil{/// <summary>/// 获取到xml文件的XDocument/// </summary>/// <param name="filePathName">xml路径和文件(如:D:\\testfile\\xmlfile.xml)</param>/// <returns></returns>public static XDocument GetXDocument(string filePathName){if (string.IsNullOrEmpty(filePathName)) return null;try{XDocument xDoc = XDocument.Load(filePathName);return xDoc;}catch (Exception ex){throw ex;}}}//Class_end
}
2.4.4、单元素和多元素对应的解释器
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;namespace InterpreterPattern.InterpreterDemoThree
{/// <summary>/// 单元素作为非终结符对应的解释器,解释并执行中间元素【面向多个父元素】/// </summary>internal class ElementExpression : ReadXmlExpression{//用来记录组合的ReadXmlExpression元素private List<ReadXmlExpression> readXmlExpressionList = new List<ReadXmlExpression>();//元素名称private string elementName = string.Empty;public ElementExpression(string elementName){this.elementName = elementName;}public bool AddElement(ReadXmlExpression readXmlExpression){readXmlExpressionList.Add(readXmlExpression);return true;}public bool RemoveElement(ReadXmlExpression readXmlExpression){readXmlExpressionList.Remove(readXmlExpression);return true;}public override string[] Interpret(Context c){/*1-获取上下文的当前元素作为父级元素*/List<XElement> parentElementList = c.PreElementList;XElement xElement = null;//查找到当前元素名称所对应的xml元素,并设置回到上下文中List<XElement> currentElementList = new List<XElement>();if (parentElementList == null || parentElementList?.Count <= 0){//说明现在获取的是根元素xElement = c.Document.Root;parentElementList.Add(xElement);c.PreElementList = parentElementList;}else{//根据父级元素和要查找的元素名称来获取当前的元素foreach (var item in parentElementList){currentElementList.AddRange(c.GetCurrentEle(item, elementName));if (currentElementList != null && currentElementList?.Count > 0){//找到一个就停止查找了break;}}List<XElement> tmpElementList = new List<XElement>();tmpElementList.Add(currentElementList[0]);c.PreElementList = tmpElementList;}//循环调用子元素的Interparet方法string[] ss = null;foreach (var item in readXmlExpressionList){ss = item.Interpret(c);}return ss;}}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;namespace InterpreterPattern.InterpreterDemoThree
{/// <summary>/// 多元素作为非终结符对应的解释器,解释并执行中间元素/// </summary>internal class MutiElementExpression : ReadXmlExpression{//用来记录组合的ReadXmlExpression元素private List<ReadXmlExpression> readXmlExpressionList = new List<ReadXmlExpression>();//元素名称private string elementName = string.Empty;public MutiElementExpression(string elementName){this.elementName = elementName;}public bool AddElement(ReadXmlExpression readXmlExpression){readXmlExpressionList.Add(readXmlExpression);return true;}public bool RemoveElement(ReadXmlExpression readXmlExpression){readXmlExpressionList.Remove(readXmlExpression);return true;}public override string[] Interpret(Context c){/*1-获取上下文的当前元素作为父级元素*/List<XElement> parentElementList = c.PreElementList;//查找到当前元素名称所对应的xml元素,并设置回到上下文中List<XElement> currentElementList = new List<XElement>();foreach (XElement element in parentElementList){currentElementList.AddRange(c.GetCurrentEle(element, elementName));}c.PreElementList = currentElementList;//循环调用子元素的Interparet方法string[] ss = null;foreach (var item in readXmlExpressionList){ss = item.Interpret(c);}return ss;}}//Class_end
}
2.4.5、单元素和属性终结符对应的解释器
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;namespace InterpreterPattern.InterpreterDemoThree
{/// <summary>/// 元素作为终结符对应的解释器【从多个父元素中去获取当前元素,若当前元素是多个,只取第一个】/// </summary>internal class ElementTerminalExpression : ReadXmlExpression{//元素的名字private string elementName = string.Empty;public ElementTerminalExpression(string elementName){this.elementName = elementName;}public override string[] Interpret(Context c){//先取出上下文中的当前元素作为父级元素List<XElement> parentElementList = c.PreElementList;//查找到当前元素名称对应的xml元素XElement element = null;if (parentElementList == null || parentElementList?.Count <= 0){//说明现在获取的是根元素element = c.Document.Root;}else{//根据父级元素和要查找的元素名称来获取当前的元素element = c.GetCurrentEle(parentElementList[0], elementName)[0];}//然后根据需要去获取这个元素的值string[] ss = new string[1];ss[0] = element.FirstNode.ToString();return ss;}}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;namespace InterpreterPattern.InterpreterDemoThree
{/// <summary>/// 属性作为终结符对应的解释器/// </summary>internal class PropertyTerminalExpression : ReadXmlExpression{//属性private string propertyName = string.Empty;public PropertyTerminalExpression(string propertyName){this.propertyName = propertyName;}public override string[] Interpret(Context c){//直接获取最后的元素属性值string[] ss = new string[1];XElement element = c.PreElementList[0];IEnumerable<XAttribute> xAttributeList = element.Attributes();foreach (XAttribute xAttribute in xAttributeList){if (xAttribute.Name.LocalName.Equals(propertyName)){ss[0] = xAttribute.Value;}}return ss;}}//Class_end
}
2.4.6、多元素和属性终结符的解释器
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;namespace InterpreterPattern.InterpreterDemoThree
{/// <summary>/// 多元素作为终结符对应的解释器/// </summary>internal class MutiElementTerminalExpression : ReadXmlExpression{//元素的名字private string elementName = string.Empty;public MutiElementTerminalExpression(string elementName){this.elementName = elementName;}public override string[] Interpret(Context c){//先取出上下文中的当前元素作为父级元素List<XElement> parentElementList = c.PreElementList;//获取当前的多个元素List<XElement> currentElementList = new List<XElement>();foreach (XElement element in parentElementList){currentElementList.AddRange(c.GetCurrentEle(element, elementName));}//获取这些元素值string[] ss = new string[currentElementList.Count];for (int i = 0; i < ss.Length; i++){ss[i] = currentElementList[i].FirstNode.ToString();}return ss;}}//Class_end
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;namespace InterpreterPattern.InterpreterDemoThree
{/// <summary>/// 以多个元素属性作为终结符对应的解释器/// </summary>internal class MutiPropertyTerminalExpression : ReadXmlExpression{//属性名称private string propertyName = string.Empty;public MutiPropertyTerminalExpression(string propertyName){this.propertyName = propertyName;}public override string[] Interpret(Context c){//直接获取最后的多个元素属性值List<XElement> elementList = c.PreElementList;string[] ss = new string[elementList.Count];for (int i = 0; i < ss.Length; i++){ss[i] = elementList[i].Attribute(propertyName).Value;}return ss;}}//Class_end
}
2.4.7、客户端测试
现在客户端实现就很简单了:
1、设计好想要取值的表达式;
2、通过解析器获取抽象语法树;
3、请求解释器解释并执行这个抽象语法树,就得到最终的结果。
namespace InterpreterPattern
{internal class Program{static void Main(string[] args){TestInterpreterDemoThree();Console.ReadLine();}/// <summary>/// 测试解释器模式示例三/// </summary>private static void TestInterpreterDemoThree(){Console.WriteLine("------测试解释器模式示例三------");string xmlFile = $"{AppContext.BaseDirectory}\\InterpreterDemoThree\\XMLFile1.xml";InterpreterDemoThree.Context context = new InterpreterDemoThree.Context(xmlFile);//通过解析器来获取抽象语法树//root/a/b/c.nameInterpreterDemoThree.ReadXmlExpression readXmlExpression = InterpreterDemoThree.Parser.Parse("root/a/b/d$.id$");//请求解析,获取返回值string[] ss=readXmlExpression.Interpret(context);foreach (var item in ss){Console.WriteLine($"d的属性id的值是【{item}】");}//如果要使用同一个上下文,连续解析,则需要重新初始化上下文对象context.ReInit();//请求解析,获取返回值//root/a/b/c$InterpreterDemoThree.ReadXmlExpression readXmlExpression2 = InterpreterDemoThree.Parser.Parse("root/a/b/d$");string[] ss2= readXmlExpression2.Interpret(context);foreach (var item in ss2){Console.WriteLine($"d的值是【{item}】");}}}//Class_end
}
2.4.8、运行结果
通过使用解释器模式,自行设计了一种简单的语法,就可以用很简单的表达式来获取你想要的xml中的值了。如果今后Xml的结构要是发生了变化,或者是想要获取不同的值,基本上就是修改客户端传入的表达式而已(如:要获取c元素的值,表达式为"root/a/b/c";要获取c元素的name属性值,表达式为"root/a/b/c.name";如果想要获取d元素的值,表达式为"root/a/b/d$"。)
三、项目源码工程
kafeiweimei/Learning_DesignPattern: 这是一个关于C#语言编写的基础设计模式项目工程,方便学习理解常见的26种设计模式https://github.com/kafeiweimei/Learning_DesignPattern