设计模式综合实战项目x-gen 系列五

本文介绍了一个设计模式实战项目,使用解释器模式来处理XML配置数据。当XML结构变化时,通过解释器模式可以灵活地解析和获取所需值。文章详细阐述了如何运用解释器模式,包括定义简单语法规则,以及解释器模式的核心代码示例,如ReadXmlExpression接口和各种解释器类的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

获取XML配置数据

1 详细功能
1.1 详细功能

为了实现theme的即配即用,规定theme的配置使用xml文件,框架的配置和模块的配置的provider都可以由用户指定,可以在运行时传入框架配置的provider,然后在 框架配置里面指定了模块配置的provider。

因此本章讲述获取默认的xml方式配置的数据。
1.2 功能边界

只是负责配置文件为xml时候的读取、解析并获取xml中的配置数据。其实就是实现前面桥接模式的抽象部分的接口。

2 内部实现

2.1 加入解释器模式

2.1.1面临的问题
自己解析xml,本来也没有什么特别困难的,但是问题就在于,如果xml文件的格式发生了变化,那么读取配置文件的程序就需要做出相应的变更,严重的时候,几乎相当于完全重写程序。

那么怎么解决当xml的结构发生改变过后,能够很方便的获取相应元素、或者是属性的值,而不用再去修改解析xml的程序呢?

2.1.2用解释器模式来解决

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

2)模式本质:
解释器模式的本质:分离实现,解释执行。

3)基础知识:
(1)解释器模式使用解释器对象来表示和处理相应的语法规则,一般一个解释器处理一条语法规则。
(2)解释器模式没有定义谁来构建抽象语法树,把这个工作交给了客户端处理
(3)使用解释器模式的时候,应该先定义好相应的语法规则,并根据规则制定解释器的语法树;然后客户端在调用解释器进行解释操作的时候,需要自行构建符合语法规则要求的语法树;而解释器模式只是负责解释并执行。
(4)从实质上看,解释器模式的思路仍然是分离、封装、简化,跟很多模式是一样的。

4)常见应用场景:

语法较为简单的自定义表达式解析,比如自定义xml文档的解析,文档结构不要太复杂,那不适合使用解释器模式。

解释器模式的基本实现,沿用了《研磨设计模式》一书的解释器模式一章中最后实现的通用解析程序,由于篇幅关系,这里就不会那么详细的讲述实现的细节,这里更关心实现的思路,比较高层一点。因此更多的细节请查看源代码或是《研磨设计模式》一书的解释器模式一章的内容。

很明显,使用解释器模式来解决这个问题是一个很好的思路。

设计一个简单的表达式规则,然后通过解释器模式来进行解析执行,从而获取到xml中的值。

约定简单的语法规则如下:
1:为表达式设计简单的文法
为了通用,用root表示根元素,a、b、c、d等来代表元素,一个简单的xml如下:

<?xml version="1.0" encoding="UTF-8"?> 12345 d1 d2 d3 d4 f1 f2 约定表达式的文法如下:

Ø获取单个元素的值:从根元素开始,一直到想要获取值的元素,元素中间用“/”分隔,根元素前不加“/”。比如表达式“root/a/b/c”就表示获取根元素下、a元素下、b元素下的c元素的值

Ø获取单个元素的属性的值:要获取值的属性一定是表达式的最后一个元素的属性,在最后一个元素后面添加“.”然后再加上属性的名称。比如表达式“root/a/b/c.name”就表示获取根元素下、a元素下、b元素下、c元素的name属性的值

Ø获取相同元素名称的值,当然是多个:要获取值的元素一定是表达式的最后一个元素,在最后一个元素后面添加“ ” 。 比 如 表 达 式 “ r o o t / a / b / d ”。比如表达式“root/a/b/d root/a/b/d”就表示获取根元素下、a元素下、b元素下的多个d元素的值的集合

Ø获取相同元素名称的属性的值,当然也是多个:要获取属性值的元素一定是表达式的最后一个元素,在最后一个元素后面添加“ ” , 然 后 在 后 面 添 加 “ . ” 然 后 再 加 上 属 性 的 名 称 , 在 属 性 名 称 后 面 也 添 加 “ ”,然后在后面添加“.”然后再加上属性的名称,在属性名称后面也添加“ .”。比如表达式“root/a/b/d . i d .id .id”就表示获取根元素下、a元素下、b元素下的多个d元素的id属性的值的集合

Ø如果要获取某个需要区分的元素下面的值,那么对于判断使用这样的语法:[属性名=值],属性名和值都不需要加引号,比如:要想获取id=”e1”的e元素下面的f元素的值,就是用这样的表达式:root/a/e$[id=e1]/f。

2:核心代码示例
(1)先来看看ReadXmlExpression的示例,示例代码如下:
/**

  • 用于处理自定义Xml取值表达式的接口
    /
    public abstractclassReadXmlExpression{
    /
    *
    • 解释表达式
    • @param c上下文
    • @return 解析过后的值,为了通用,可能是单个值,也可能是多个值,
    • 因此就返回一个数组
      */
      publicabstract String[] interpret(Context c);
      }

(2)看看以单个元素作为中间元素的实现,要注意跟以前的实现相比,添加了对条件的判断,示例代码如下:
/**

  • 单个元素作为非终结符的解释器
    /
    public class ElementExpression extends ReadXmlExpression{
    /
    *

    • 用来记录组合的ReadXmlExpression元素
      /
      private List eles = new ArrayList();
      /
      *
    • 元素的名称
      /
      private String eleName = “”;
      /
      *
    • 表达式中设置的条件表达式
      */
      private String condition = “”;
      public ElementExpression(String eleName,String condition){
      this.eleName = eleName;
      this.condition = condition;
      }
      public boolean addEle(ReadXmlExpression ele){
      this.eles.add(ele);
      return true;
      }
      public boolean removeEle(ReadXmlExpression ele){
      this.eles.remove(ele);
      return true;
      }
      public boolean removeAllEle(){
      this.eles.clear();
      return true;
      }

    public String[] interpret(Context c) {
    //先取出上下文里的父级元素
    List pEles = c.getPreEles();
    Element ele = null;
    //把当前获取的元素放到上下文里面,这次是获取多个元素
    List nowEles = new ArrayList();
    if(pEles.size()==0){
    //说明现在获取的是根元素
    ele = c.getDocument().getDocumentElement();
    pEles.add(ele);
    c.setPreEles(pEles);
    }else{
    for(Element tempEle : pEles){
    nowEles.addAll(c.getNowEles(tempEle, eleName));
    if(nowEles.size()>0){
    //找到一个就停止
    break;
    }
    }

    List tempList = new ArrayList();
    //判断是否满足条件,满足条件才放入
    if(nowEles.size()>0 && c.judgeCondition(nowEles.get(0), condition)){
    tempList.add(nowEles.get(0));
    c.setPreEles(tempList);
    }
    }

      //循环调用子元素的interpret方法
      String [] ss = null;
      for(ReadXmlExpression tempEle : eles){
           ss = tempEle.interpret(c);
      }
      return ss;
    

    }
    public void setEles(List eles) {
    this.eles = eles;
    }
    public List getEles() {
    return eles;
    }
    }

(3)看看以多个元素作为中间元素的实现,要注意跟以前的实现相比,添加了对条件的判断,示例代码如下:
/**

  • 多个元素做为非终结符的解释处理对象
    /
    public class ElementsExpression extends ReadXmlExpression{
    /
    *

    • 用来记录组合的ReadXmlExpression元素
      /
      privateList eles = new ArrayList();
      /
      *

    • 元素名字
      */
      private String eleName = “”;
      private String condition;
      publicElementsExpression(String eleName,String condition){
      this.eleName = eleName;
      this.condition = condition;
      }
      public booleanremoveAllEle(){
      this.eles.clear();
      return true;
      }
      public String[] interpret(Context c) {
      //先取出上下文里的父级元素
      List pEles = c.getPreEles();
      //把当前获取的元素放到上下文里面,这次是获取多个元素
      List nowEles = new ArrayList();

      for(Element ele : pEles){
      nowEles.addAll(c.getNowEles(ele,eleName));
      }

      //循环判断是否满足条件,满足条件才放入
      Iterator it = nowEles.iterator();
      while(it.hasNext()){
      Element ele = (Element)it.next();
      if(!c.judgeCondition(ele, condition)){
      it.remove();
      }
      }
      c.setPreEles(nowEles);

      //循环调用子元素的interpret方法
      String [] ss = null;
      for(ReadXmlExpression ele : eles){
      ss = ele.interpret©;
      }
      return ss;
      }

    public booleanaddEle(ReadXmlExpression ele){
    this.eles.add(ele);
    return true;
    }
    public booleanremoveEle(ReadXmlExpression ele){
    this.eles.remove(ele);
    return true;
    }
    public voidsetEles(List eles) {
    this.eles = eles;
    }
    publicList getEles() {
    return eles;
    }
    }

(4)看看以单个元素作为终结符元素的实现,要注意跟以前的实现相比,添加了对条件的判断,示例代码如下:
/**

  • 元素作为终结符对应的解释器
    /
    public classElementTerminalExpression extendsReadXmlExpression{
    /
    *

    • 元素的名字
      */
      private String eleName = “”;
      private String condition;
      publicElementTerminalExpression(String name,String condition){
      this.eleName = name;
      this.condition = condition;
      }

    public String[] interpret(Context c) {
    //先取出上下文里的当前元素作为父级元素
    List pEles = c.getPreEles();
    //查找到当前元素名称所对应的xml元素
    Element ele = null;
    if(pEles.size() == 0){
    //说明现在获取的是根元素
    ele = c.getDocument().getDocumentElement();
    }else{
    //获取当前的元素
    ele = c.getNowEles(pEles.get(0),eleName).get(0);
    }

    //判断是否满足条件
    if(!c.judgeCondition(ele,condition)){
    return new String[0];
    }

    //然后需要去获取这个元素的值
    String[] ss = new String[1];
    if(ele.getFirstChild()!=null){
    ss[0] = ele.getFirstChild().getNodeValue();
    }else{
    ss[0] = “”;
    }
    return ss;
    }
    }

(5)看看以多个元素作为终结符元素的实现,要注意跟以前的实现相比,添加了对条件的判断,示例代码如下:
/**

  • 以多个元素作为终结符的解释处理对象
    /
    public class ElementsTerminalExpression extends ReadXmlExpression{
    /
    *
    • 元素的名称
      */
      private String eleName = “”;
      private String condition;
      public ElementsTerminalExpression(String name,String condition){
      this.eleName = name;
      this.condition = condition;
      }
      public String[] interpret(Context c) {
      //先取出上下文里的父级元素
      List pEles = c.getPreEles();
      //获取当前的多个元素
      List nowEles = newArrayList();

      for(Element ele : pEles){
      nowEles.addAll(c.getNowEles(ele,eleName));
      }
      //循环判断是否满足条件,满足条件才放入
      Iterator it = nowEles.iterator();
      while(it.hasNext()){
      Element ele = (Element)it.next();
      if(!c.judgeCondition(ele,condition)){
      it.remove();
      }
      }
      //然后需要去获取这些元素的值
      String[] ss = newString[nowEles.size()];
      for(int i=0;i<ss.length;i++){
      if(nowEles.get(i).getFirstChild()!=null){
      ss[i] = nowEles.get(i).getFirstChild().getNodeValue();
      }else{
      ss[i] = “”;
      }
      }
      return ss;
      }
      }

(6)看看以单个属性作为终结符元素的实现,示例代码如下:
/**

  • 属性作为终结符对应的解释器
    /
    public class PropertyTerminalExpression extendsReadXmlExpression{
    /
    *

    • 属性的名字
      /
      private String propName;
      /
      *
    • 保留condition是为了今后扩展需要
      */
      public PropertyTerminalExpression(String propName,String condition){
      this.propName = propName;
      }

    public String[] interpret(Context c) {
    //直接获取最后的元素的属性的值
    String[] ss = new String[1];
    ss[0] = c.getPreEles().get(0).getAttribute(this.propName);
    return ss;
    }
    }

(7)看看以多个属性作为终结符元素的实现,示例代码如下:
/**

  • 以多个元素的属性做为终结符的解释处理对象
    /
    public class PropertysTerminalExpression extends ReadXmlExpression{
    /
    *

    • 属性名字
      */
      private String propName;
      // private String condition;
      publicPropertysTerminalExpression(String propName,String condition){
      this.propName = propName;
      // this.condition = condition;
      }

    public String[] interpret(Context c) {
    //获取最后的多个元素
    List eles = c.getPreEles();

    String[] ss = newString[eles.size()];
    //循环多个元素,获取每个的属性的值
    for(int i=0;i<ss.length;i++){
    ss[i] = eles.get(i).getAttribute(this.propName);
    }
    return ss;
    }
    }

(8)该来看看上下文的实现了,注意跟以前的实现相比,上下文里面真正实现了条件的判断,作为公共的功能提供给各个解释器使用,示例代码如下:
/**

  • 上下文,用来包含解释器需要的一些全局信息
    /
    public class Context {
    /
    *

    • Dom解析Xml的Document对象
      /
      private Document document = null;
      /
      *

    • 上一次被处理的多个元素
      /
      private List preEles = new ArrayList();
      /
      *

    • 构造方法

    • @param filePathName 需要读取的xml的路径和名字

    • @throws Exception
      /
      public Context(String filePathName) throws Exception{
      //通过辅助的Xml工具类来获取被解析的xml对应的Document对象
      this.document = XmlUtil.getRoot(filePathName);
      }
      /
      *

    • 各个Expression公共使用的方法,

    • 根据父元素和当前元素的名称来获取当前的多个元素的集合

    • @param pEle 父元素

    • @param eleName 当前元素的名称

    • @return 当前的多个元素的集合
      */
      public List getNowEles(Element pEle,String eleName){
      List elements = newArrayList();
      NodeList tempNodeList = pEle.getChildNodes();
      for(inti=0;i<tempNodeList.getLength();i++){
      if(tempNodeList.item(i)instanceof Element){
      Element nowEle = (Element)tempNodeList.item(i);
      if(nowEle.getTagName().equals(eleName)){
      elements.add(nowEle);
      }
      }
      }
      return elements;
      }
      public booleanjudgeCondition(Element ele,String condition){
      //目前只是支持判断元素的属性等于某个值
      if(condition==null || condition.trim().length()==0){
      return true;
      }

      String ss[] = condition.split("=");
      if(ss[1]!=null && ss[1].equals(ele.getAttribute(ss[0]))){
      return true;
      }
      return false;
      }

    public void init(){
    preEles = newArrayList();
    }

    public Document getDocument() {
    return document;
    }
    public List getPreEles() {
    return preEles;
    }
    public voidsetPreEles(List nowEles) {
    this.preEles = nowEles;
    }
    }
    本文由微信公众号——架构设计一起学 推出,
    可点击“阅读原文”了解更多!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值