19. 解释器模式+设计模式七大原则

19 解释器模式

  1. 给定一个语言(xml),定义它的文法的一种表示(语法规则、root/a/b/c表示c节点的值),并定义一个解释器(AbstractExpression),这个解释器使用该表示来解释语言中的句子(xml)
  2. 类图
    1. AbstractExpression:解释器接口,约定解释的方法名、返回值
    2. TerminalExpression:终结符解释器,用于解释语法规则中和终结符相关的操作。如果使用组合模式构建抽象语法树时,就相当于叶子对象
      1. 终结符: 不能再进行拆分的元素
    3. NonterminalExpression:非终结符解释器,使用组合模式时,相当于组合对象
      1. 非终结符: 可拆分元素
    4. Context:上下文,通常包含各个解释器需要的数据或是公共的功能(可以获取xml数据内容)
    5. Client:指使用解释器的客户端,将表达式,按语言的语法转换为使用解释器对象表示的抽象语法树,然后调用解释操作

在这里插入图片描述

19.1 使用场景

  1. 解析xml时,从中获取值时,每次都要写大量代码对其解析,并获得各元素值,一旦xml格式改变,又要修改解析代码,从而获取元素值

19.2 解决方案

  1. 先使用解析器(不是解析xml解析器)对客户要调用的表达式(root/a/b/c)进行解析,形成一个抽象语法树(AbstractExpression)
    1. 注意这个解析器并不是解析xml用的,而是将客户要调用的表达式进行解析
    2. xml的解析器是在解释抽象语法树时作为辅助方法使用的,因为没有xml解析器获取xml文本中的节点的值,也就没法得到解释结果
  2. 使用解释器对象来表示这棵抽象语法树,并执行语法树中各节点功能,从而得到表达式的结果
  3. 抽象语法树
    在这里插入图片描述

19.3 代码

  1. 本例中,省略了解析器将客户指定表达式(root/a/b/c)解析成语法树的操作,而是直接组建了一个含义为(root/a/b/c)的语法树
  2. E:\IdeaProjects\DesignPattern\InterpreterTest.xml:为语言本身
<?xml version="1.0" encoding="UTF-8"?>
  <root id="rootId">
    <a>
      <b>
        <c name="testC">12345</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. ReadXmlExpression:AbstractExpression
package interpret.pattern1;

public abstract class ReadXmlExpression {
    /**
     * 解释表达式
     * @param c 上下文
     * @return 解析过后的值,为了通用,可能是单个值,也可能是多个值,
     *         因此就返回一个数组
     */
    public abstract String[] interpret(Context c);
}
  1. ElementExpression
package interpret.pattern1;

import org.w3c.dom.Element;

import java.util.ArrayList;
import java.util.Collection;

//root/a/b/c中,root、a、b都是非终结符,需要用非终结符解释器进行解析
//c为终结符,需使用终结符解释器进行解析
//终结符解释器与非终结符的解释器的解析方法返回的内容并不一样,因为非终结符,表示该表达式root/a/b/c后面还有其他符号,因此会调用下一层符号的解释器方法
//而终结符后面不再有符号,因此直接回返回节点的值
public class ElementExpression extends ReadXmlExpression{
    //组合模式实现语法树,非终结符解释器相当于组合,因此需要一个eles引用,存放该组合内的叶子节点
    private Collection<ReadXmlExpression> eles = new ArrayList<ReadXmlExpression>();
    //元素名称
    private String eleName = "";
    public ElementExpression(String eleName){
        this.eleName = eleName;
    }
    //为组合添加叶子节点
    public boolean addEle(ReadXmlExpression ele){
        this.eles.add(ele);
        return true;
    }
    public boolean removeEle(ReadXmlExpression ele){
        this.eles.remove(ele);
        return true;
    }
    @Override
    public String[] interpret(Context c) {
        //获取该非终结符的上一层的符号,用于判断该非终结符,是否是表达式root/a/b/c中的root
        //获取pEle是因为,比如为了方便定位xml中,b代表的具体的Element,需要知道b的上层Element,所以当解析a时,就应该将a放入到Context中的preEle中进行存放
        //这样,对b解析时,就能通过Context找到其上层元素a

        //查找到当前元素名称所对应的xml元素,并设置回到上下文中
        Element pEle = c.getPreEle();
        if(pEle==null){
            //说明现在获取的是根元素
            c.setPreEle(c.getDocument().getDocumentElement());
        }else{
            //根据父级元素和要查找的元素的名称来获取当前的元素
            Element nowEle = c.getNowEle(pEle, eleName);
            //把当前获取的元素放到上下文里面
            c.setPreEle(nowEle);
        }

        //循环调用子元素的interpret方法
        //对于非终结操作解释器,应该调用其叶子节点解释器中的解释方法
        String [] ss = null;
        for(ReadXmlExpression ele : eles){
            ss = ele.interpret(c);
        }
        return ss;
    }
}
  1. ElementTerminalExpression
package interpret.pattern1;

import org.w3c.dom.Element;

/**
 * 元素作为终结符对应的解释器
 */
public class ElementTerminalExpression  extends ReadXmlExpression{
    /**
     * 元素的名字
     */
    private String eleName = "";
    public ElementTerminalExpression(String name){
        this.eleName = name;
    }

    public String[] interpret(Context c) {
        //先取出上下文里的当前元素作为父级元素
        Element pEle = c.getPreEle();
        //查找到当前元素名称所对应的xml元素
        Element ele = null;
        if(pEle==null){
            //说明现在获取的是根元素
            ele = c.getDocument().getDocumentElement();
            c.setPreEle(ele);
        }else{
            //根据父级元素和要查找的元素的名称来获取当前的元素
            ele = c.getNowEle(pEle, eleName);
            //把当前获取的元素放到上下文里面
            c.setPreEle(ele);
        }

        //然后需要去获取这个元素的值
        //终结操作解释器中,进行解释时,直接返回该xml节点的值
        String[] ss = new String[1];
        ss[0] = ele.getFirstChild().getNodeValue();
        return ss;
    }
}

  1. PropertyTerminalExpression
package interpret.pattern1;

/**
 * 属性作为终结符对应的解释器
 */
public class PropertyTerminalExpression extends ReadXmlExpression{
    /**
     * 属性的名字
     */
    private String propName;
    public PropertyTerminalExpression(String propName){
        this.propName = propName;
    }

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

  1. Context
package interpret.pattern1;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

import java.util.ArrayList;
import java.util.List;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
/**
 *  上下文,用来包含解释器需要的一些全局信息
 */
public class Context {
    /**
     * 上一个被处理的元素
     */
    private Element preEle = null;
    /**
     * Dom解析Xml的Document对象
     */
    private Document document = null;
    /**
     * 构造方法
     * @param filePathName 需要读取的xml的路径和名字
     * @throws Exception
     */
    public Context(String filePathName) throws Exception{
        //通过辅助的Xml工具类来获取被解析的xml对应的Document对象
        this.document = XmlUtil.getRoot(filePathName);
    }
    /**
     * 重新初始化上下文
     */
    public void reInit(){
        preEle = null;
    }
    /**
     * 各个Expression公共使用的方法,
     * 根据父元素和当前元素的名称来获取当前的元素
     * @param pEle 父元素
     * @param eleName 当前元素的名称
     * @return 找到的当前元素
     */
    public Element getNowEle(Element pEle,String eleName){
        NodeList tempNodeList = pEle.getChildNodes();
        for(int i=0;i<tempNodeList.getLength();i++){
            if(tempNodeList.item(i) instanceof Element){
                Element nowEle = (Element)tempNodeList.item(i);
                if(nowEle.getTagName().equals(eleName)){
                    return nowEle;
                }
            }
        }
        return null;
    }

    public Element getPreEle() {
        return preEle;
    }
    public void setPreEle(Element preEle) {
        this.preEle = preEle;
    }

    public Document getDocument() {
        return document;
    }
}
  1. XmlUtil:用于辅助Context,从xml中获取数据
package interpret.pattern1;

import org.w3c.dom.Document;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

public class XmlUtil {
    public static Document getRoot(String filePathName) throws Exception{
        Document doc = null;
        //建立一个解析器工厂
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        //获得一个DocumentBuilder对象,这个对象代表了具体的DOM解析器
        DocumentBuilder builder=factory.newDocumentBuilder();
        //得到一个表示XML文档的Document对象
        doc=builder.parse(filePathName);
        //去掉XML文档中作为格式化内容的空白而映射在DOM树中的不必要的Text Node对象
        doc.normalize();
        return doc;
    }
}
  1. Client
package interpret.pattern1;

public class Client {
    public static void main(String[] args) throws Exception {
        //准备上下文
        Context c = new Context("InterpreterTest.xml");

        //想要获取c元素的值,也就是如下表达式的值:"root/a/b/c"
        //1. 注意此处省略了将root/a/b/c解析成解释器对象表示的语法树的方法,这部分内容属于解析器的功能
        //2. 此处直接创建表达root/a/b/c的语法树的解释器对象
        //首先要构建解释器的抽象语法树
        ElementExpression root = new ElementExpression("root");
        ElementExpression aEle = new ElementExpression("a");
        ElementExpression bEle = new ElementExpression("b");
        ElementTerminalExpression cEle = new ElementTerminalExpression("c");
        //组合起来
        root.addEle(aEle);
        aEle.addEle(bEle);
        bEle.addEle(cEle);

        //调用
        String ss[] = root.interpret(c);
        System.out.println("c的值是=" + ss[0]);


//      //想要获取c元素的name属性,也就是如下表达式的值:"root/a/b/c.name"
//      //这个时候c不是终结了,需要把c修改成ElementExpressioin
//      ElementExpression root = new ElementExpression("root");
//      ElementExpression aEle = new ElementExpression("a");
//      ElementExpression bEle = new ElementExpression("b");
//      ElementExpression cEle = new ElementExpression("c");
//      PropertyTerminalExpression prop = new PropertyTerminalExpression("name");
//      //组合
//      root.addEle(aEle);
//      aEle.addEle(bEle);
//      bEle.addEle(cEle);
//      cEle.addEle(prop);
//
//      //调用
//      String ss[] = root.interpret(c);
//      System.out.println("c的属性name的值是="+ss[0]);
//
//      //如果要使用同一个上下文,连续进行解析,需要重新初始化上下文对象
//      //比如要连续的重新再获取一次属性name的值,当然你可以重新组合元素,
//      //重新解析,只要是在使用同一个上下文,就需要重新初始化上下文对象
//      c.reInit();
//      String ss2[] = root.interpret(c);
//      System.out.println("重新获取c的属性name的值是="+ss2[0]);
    }
}

19.4 设计模式七大原则

19.4.1 七大原则的目标
  1. 可维护性
    1. 修改功能,需要改动的地方越少,可维护性越好
  2. 可复用性
    1. 代码可以被以后重复使用
    2. 写出自己总结的类库
  3. 可拓展性
    1. 添加功能,无需/少量修改原来代码
  4. 灵活性
    1. 代码的接口设计的比较灵活,多暴露几个重载方法
19.4.2 七大原则
  1. 单一职责:类的职责要单一,不要将太多的职责放在同一个类中
    1. Person/PersonManager
    2. 高内聚/低耦合
  2. 开闭原则:对扩展开放,对修改关闭
    1. 尽量不修改原来代码的情况下进行扩展
    2. 抽象化/多态,是开闭原则的关键
  3. 里氏替换:所有使用父类的地方,必须能够透明的使用子类对象
    1. F a =new F();new后面这个F,可以用其任何子类替换,而不影响客户端其他功能
    2. 子类如果重写了父类的方法,但修改了原来的语义,就属于不符合里氏替换原则
  4. 依赖倒置:依赖抽象而不是具体,即面相接口编程
    1. 因为里面具体实现就可以随意替换了
  5. 接口隔离原则:每一个接口应该承担独立的角色,不干不该自己干的事
    1. Flyable/Runnable不应合二为一,因为如果合二为一,子类只想实现Flyable时,也需要重写Runnable中的方法,用起来不方便
    2. 需要对客户提供接口时,只需要暴漏最小的接口,如果不这样,如果客户只需要用到fly方法,但变成时,将run方法也暴漏给客户,容易导致用错
  6. 迪米特法则:尽量不要和陌生人说话,可以降低与其他类的耦合
  7. 合成复用原则:在系统中应该尽量多使用组合和聚合关联关系,尽量少使用甚至不使用继承关系
发布了67 篇原创文章 · 获赞 1 · 访问量 2052
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览