文档章节

解释器模式_实战

grace_233
 grace_233
发布于 01/20 19:25
字数 2065
阅读 24
收藏 0

前言

解释器模式是什么?这个设计模式其实比较冷门,不太会解释,用例子说明把。解释器模式一般用在sql,xml,json解析等场景。比如说你有一个json对象,你要获取这个对象中任意一个节点的值。 你怎么做呢?你当然可以一层层的遍历去获取,但是这么做肯定代码是很丑很难维护的(下面会举例说明)。 所以解释器模式就是说定义一种语法规则,比如定义a/b/c/d,就表示你要拿a->b->c->d这个节点的值。 那么你封装一个抽象语法树,把a/b/c/d解释成一个语法树。 构造一个解析器,你要拿值的时候把json对象传进去,由解析器负责去解析这个语法树,获取你要的值。

实战

案例

在之前做的一个项目中,有这样一个业务场景,从下游接口拿到的json对象,很复杂。我们要把这个json在我们的库里面存一份,但是呢这个json对象中有敏感信息,比如手机号,身份证号等信息,要求把这些敏感值加密存储, 取出来的时候再解密。

不用设计模式

先看一下不用设计模式,我写的这段代码,看下是什么感受:

json对象:

{
    "a": {
        "b": {
            "mobile": "12345678911",
            "idcard": "513721111111111111111",
            "type": ["dede", "xijswsw", "kijde"]
        }
    },
    "c": {
        "d": "aaa",
        "e": [{
                "name": "zhangsan",
                "age": 24,
                "f": {
                    "g": "jdiejdedew"
                }

            }, {
                "name": "lisi",
                "age": 24
            }
        ]
    }
}

Test.java:

首先很自然的可以想到,可以用a/b/c/d这种规则去定义我要加密的节点。

package com.xll.json.fastjson.parser.test;

import java.io.IOException;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;

public class Test2 {

    /**
     * 对字符串简单加密,就是把中间的字符用***替换
     * 
     * @param key
     * @return
     */
    public static String doEncrypt(String key) {
        return key.substring(0, 1) + "***" + key.substring(key.length() - 2);
    }

    public static void encrypt(JSONObject obj, String key) {
        Object value = obj.get(key);
        if (value instanceof String) {
            // 值是string,就直接加密
            obj.put(key, Test2.doEncrypt((String) value));
        }
        if (value instanceof JSONArray) {
            // 值是数组的话,遍历数组的每个值,进行加密
            JSONArray value2 = (JSONArray) value;
            for (int i = 0; i < value2.size(); i++) {
                value2.set(i, Test2.doEncrypt(value2.getString(i)));
            }
            obj.put(key, value2);
        }
    }

    public static void main(String[] args) throws IOException {
        // 获取json对象
        String str = FileUtils.getStr(Test1.class.getResource("a.json").getPath());
        JSONObject oldObj = (JSONObject) JSON.parse(str);
        System.out.println("加密前:" + oldObj.toJSONString());

        JSONObject obj = oldObj;
        String expression = "a/b/type";
        String[] nodes = expression.split("/");
        for (int i = 0; i < nodes.length; i++) {
            if (i == nodes.length - 1) {
                // 到达了叶子节点,调用加密方法
                Test2.encrypt(obj, nodes[i]);
            } else {
                // 没有到叶子节点的话,就继续遍历
                obj = (JSONObject) obj.get(nodes[i]);
            }
        }
        System.out.println("加密后:" + oldObj.toJSONString());
    }
}

这段代码没有考虑非叶子节点有数组的情况,所以肯定是不行的。 即使说现在只考虑叶子节点存在数组的情况,这样的代码逻辑还是很丑。json遍历和调用加密的方法是耦合在一起的,如果说以后需要变更,假设说要做加密,还要做格式转换,那岂不是还得去遍历json的代码中加一段调用格式转换的代码么。 不便于维护和扩展。 所以说要改造得高内聚低耦合的话,至少说你要把json遍历的代码封装起来,不对外暴露。

使用解释器模式

搞一个比较复杂的json:

[{
    "a": {
        "b": {
            "mobile": "12345678911",
            "idcard": "513721111111111111111",
            "type": ["dede", "xijswsw", "kijde"]
        }
    },
    "c": {
        "d": "aaa",
        "e": [{
                "name": "zhangsan",
                "age": 24,
                "f": {
                    "g": "jdiejdedew"
                }

            }, {
                "name": "lisi",
                "age": 24
            }
        ]
    }
}, 
{
    "a": {
        "b": {
            "mobile": "12345678911",
            "idcard": "513721111111111111111",
            "type": ["dede", "xijswsw", "kijde"]
        }
    }
}
]

解释器模式就是把我们自己定义的语法规则"a/b/c/d"解释成一颗抽象语法树。

Element接口

先定义一个语法树节点的接口:

package com.xll.json.fastjson.parser.encrypt;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;

public interface Element {

    /**
     * 解析JSONObject对象中的值
     * 
     * @param currentObject
     */
    void parse(JSONObject currentObject);

    /**
     * 解析JSONArray对象中的值<br>
     * 数组需要遍历每一个数组元素,调用childElement的encrypt方法
     * 
     * @param currentObject
     */
    void parse(JSONArray currentObject);

    /**
     * 解析JSON对象
     * 
     * @param currentObject
     */
    default void parse(JSON currentObject) {
        if (currentObject instanceof JSONObject) {
            parse((JSONObject) currentObject);
        }
        if (currentObject instanceof JSONArray) {
            parse((JSONArray) currentObject);
        }
    }
}

再实现两种类型的节点类。MiddleElement(中间节点)和LeafElement(叶子节点)。

MiddleElement

package com.xll.json.fastjson.parser.encrypt;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;

/**
 * 中间节点,非叶子节点
 * 
 * @author leiqian
 *
 */
public class MiddleElement implements Element {
    /** 子element */
    private Element childElement;
    /** 当前节点的path */
    private String path;

    public MiddleElement(String path) {
        this.path = path;
        init();
    }

    /**
     * 初始化,构造抽象语法树
     */
    public void init() {
        String[] keys = this.path.split("/");
        if (keys.length > 2) {
            this.childElement = new MiddleElement(this.path.substring(keys[0].length() + 1));
        } else {
            this.childElement = new LeafElement(keys[1]);
        }
    }

    @Override
    public void parse(JSONObject currentObject) {
        Object childObject = currentObject.get(this.path.split("/")[0]);
        this.childElement.parse((JSON) childObject);
    }

    @Override
    public void parse(JSONArray currentObject) {
        for (int i = 0; i < currentObject.size(); i++) {
            parse((JSON) currentObject.get(i));
        }
    }

}

LeafElement

package com.xll.json.fastjson.parser.encrypt;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;

/**
 * 叶子节点
 * 
 * @author leiqian
 *
 */
public class LeafElement implements Element {
    /** 叶子节点的key */
    private String key;

    public LeafElement(String key) {
        this.key = key;
    }

    @Override
    public void parse(JSONObject currentObject) {
        Object value = currentObject.get(key);
        // 值是string类型,直接对值加密
        if (value instanceof String) {
            currentObject.put(key, EncryptUtils.doEncrypt((String) value));
        }
        // 值是数组类型,对数组中的每个值分别加密
        if (value instanceof JSONArray) {
            JSONArray value2 = (JSONArray) value;
            for (int i = 0; i < value2.size(); i++) {
                value2.set(i, EncryptUtils.doEncrypt(value2.getString(i)));
            }
            currentObject.put(key, value2);
        }
    }

    @Override
    public void parse(JSONArray currentObject) {
        for (int i = 0; i < currentObject.size(); i++) {
            parse((JSONObject) currentObject.get(i));
        }

    }

}

加密工具类:

package com.xll.json.fastjson.parser.encrypt;

public class EncryptUtils {
    /**
     * 对单个string加密。简单的把字符串中间的字符替换成***
     * 
     * @param key
     * @return
     */
    public static String doEncrypt(String key) {
        return key.substring(0, 1) + "***" + key.substring(key.length() - 2);
    }

}

Json解析器类

package com.xll.json.fastjson.parser.encrypt;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

/**
 * Json解析器
 * 
 * @author leiqian
 *
 */
public class JSONParser {
    private String path;
    private Element rootElement;

    public JSONParser(String path) {
        this.path = path;
    }

    /**
     * 先把path表达式解析成抽象语法树,返回一个解析器对象。然后再去调用加密方法<br>
     * 不直接使用静态方法加密的原因是,你可以把这个JSONParser自己给缓存起来,方便重复利用
     * 
     * @param path
     * @return
     */
    public static JSONParser compile(String path) {
        String[] arr = path.split("/");
        if (arr.length == 0)
            return null;
        JSONParser parser = new JSONParser(path);
        if (arr.length == 1) {
            parser.rootElement = new LeafElement(arr[0]);
        } else {
            parser.rootElement = new MiddleElement(path);
        }
        return parser;
    }

    public void encrypt(JSON object) {
        this.rootElement.parse(object);
    }

    public static void encrypt(JSONObject object, String path) {
        JSONParser parser = JSONParser.compile(path);
        parser.encrypt(object);
    }

}

测试方法

package com.xll.json.fastjson.parser.encrypt;

import com.alibaba.fastjson.JSON;
import com.xll.json.fastjson.parser.test.FileUtils;
import com.xll.json.fastjson.parser.test.Test1;

public class Test {
    public static void main(String[] args) {
        // 获取json对象
        String str = FileUtils.getStr(Test1.class.getResource("a.json").getPath());
        JSON obj = (JSON) JSON.parse(str);
        System.out.println("加密前:" + obj.toJSONString());
        // 抽象表达式,用于定位到具体的json节点
        String[] path = new String[] { "a/b/type", "a/b/mobile", "c/e/name", "c/e/f/g" };
        for (int i = 0; i < path.length; i++) {
            JSONParser parser = JSONParser.compile(path[i]);
            parser.encrypt(obj);
        }
        System.out.println("加密后:" + obj.toJSONString());
    }
}

执行结果:

加密前:[{"a":{"b":{"idcard":"513721111111111111111","mobile":"12345678911","type":["dede","xijswsw","kijde"]}},"c":{"d":"aaa","e":[{"f":{"g":"jdiejdedew"},"name":"zhangsan","age":24},{"name":"lisi","age":24}]}},{"a":{"b":{"idcard":"513721111111111111111","mobile":"12345678911","type":["dede","xijswsw","kijde"]}}}]
加密后:[{"a":{"b":{"idcard":"513721111111111111111","mobile":"1***11","type":["d***de","x***sw","k***de"]}},"c":{"d":"aaa","e":[{"f":{"g":"j***ew"},"name":"z***an","age":24},{"name":"l***si","age":24}]}},{"a":{"b":{"idcard":"513721111111111111111","mobile":"1***11","type":["d***de","x***sw","k***de"]}}}]

代码逻辑不解释了,看注释就行。

总结

沿着这样的思路,除了节点的加解密之外,你还可以封装一些别的业务逻辑,比如说做复杂json的数据校验等。

另外,实际上你要获取JSON对象的节点也不用自己去造轮子,fastjson也给你封装了JSONPath,定义了一套语法规则,方便你去定位节点。一般用JSONPath就够了, 除非你的业务场景比较复杂,你再去自己封装一层。 可以去详细看一下它的源码是怎么实现的。

关于设计模式的思考

其实比较一下上面不用设计模式和用了设计模式的代码区别。你会发现虽然用了设计模式之后代码量变得庞杂了。但是要遍历json,遍历的时候还要考虑是对象节点还是数组节点,如果不用设计模式的话代码肯定就是比较复杂难以维护的。 所以说用了设计模式,你会发现每个方法里面的代码逻辑都变得很清晰,浅显易懂了。 所以说这就是合理运用设计模式可以提高代码的扩展性,可维护性,易读性的原因吧。

© 著作权归作者所有

共有 人打赏支持
grace_233
粉丝 7
博文 91
码字总数 40137
作品 0
浦东
程序员
私信 提问
Android中的设计模式之解释器模式

参考 《设计模式:可复用面向对象软件的基础 》5.3 Interpreter 解释器 类行为型模式 《Android源码设计模式解析与实战》第10章 化繁为简的翻译机--解释器模式 意图 给定一个语言,定义它的文...

newtrek
2018/08/19
0
0
Java设计模式之-解释器模式(Interpreter)

解释器模式在我看来,应用范围很小,我想了很久,想举一个不是加减乘除运算的例子出来,但是很难......在设计模式一书中,它的意图也被定义为实现一种语言所要运用的模式,我在网上找了一篇比...

Mock2052
2017/12/12
0
0
解析器模式

1 场景问题 1.1 读取配置文件 考虑这样一个实际的应用,维护系统自定义的配置文件。 几乎每个实际的应用系统都有与应用自身相关的配置文件,这个配置文件是由开发人员根据需要自定义的,系统...

ciyo_yang
2017/07/15
0
0
Android 架构师之路12 设计模式之解释器模式

Android 架构师之路 目录 1、解释器模式概念 1.1 介绍 解释器模式(Interpreter Pattern)提供了评估语言的语法或表达式的方式,它属于行为型模式。这种模式实现了一个表达式接口,该接口解释...

香沙小熊
2018/01/29
0
0
23种设计模式(14):解释器模式

定义:给定一种语言,定义他的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中句子。 类型:行为类模式 类图: 解释器模式是一个比较少用的模式,本人之前也没有用过这个...

LCZ777
2014/07/09
0
0

没有更多内容

加载失败,请刷新页面

加载更多

[Android O] Camera 服务启动流程简析

前言 去年正式进入框架组的时候,啥也不会,瞎jb分析了一通 Android N 上面的 Camera 相关流程。其实基本上都是跟着别人的分析日志看代码,然后按照自己的理解记了些笔记而已。 不过当时感觉...

天王盖地虎626
1分钟前
0
0
MySql 常用函数

一、字符串函数 contact(s1,s2,s3...) : 把传入的参数连接成字符串 mysql> select concat('a','b','c'); +---------------------+ | concat('a','b','c') | +---------------------+ | abc |......

嘴角轻扬30
2分钟前
0
0
通过Spark进行ALS离线和Stream实时推荐

ALS简介 ALS是alternating least squares的缩写 , 意为交替最小二乘法;而ALS-WR是alternating-least-squares with weighted-λ -regularization的缩写,意为加权正则化交替最小二乘法。该方...

东风飘兮神灵雨
2分钟前
0
0
Twemproxy增加或剔除Redis节点后对数据有何影响

本篇文章,Twemproxy增加或剔除Redis节点后对数据的影响是接着”通过Twemproxy代理Redis数据分片方案“这篇文章写的。最好还要懂一致性哈希(ketama)的原理。 上一篇文章中,我们配置了一个...

linuxprobe16
6分钟前
0
0
Java魔法类——Unsafe应用解析

前言 Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等,这些方法在提升Java运行效率、增强Java语言底层资源...

微笑向暖wx
6分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部