java设计模式之解释器
解释器模式听起来很复杂,但应用范围很小众,只会在一些特定领域才会用到,比如编译器、规则引擎,正则表达式。
正好我在项目中用到了这个功能。作为例子总结一下。
我的项目中实现了一个API注册的功能,简单来说就是用户通过注册一个数据源和一个SQL,我给他返回一个API接口。
本身逻辑并不复杂,但因为很多指标是跟日期相关的。需要给SQL语句提供一个宏变量占位符的功能,这样才能保证随着时间的变化,用户可能想要看本日、昨日、本月、上月这些日期维度的数据,我们在不改变SQL的情况下,都能满足这些需求。
显然这些日期不能再SQL中写死。我们需要提供一种规则,用户按规则填写一个字符串,比如"${yyyy-MM-dd:-1d}",我再SQL执行之前,将它替换成昨天的日期。
在下面的实现中,我使用了两次正则表达式,第一次是将"${}"中的字符串识别为宏变量,第二次才是对宏变量的解释。
一、宏变量的提取
public class DateFormatUtil { private static final Pattern dynamicPattern = Pattern.compile("\\$\\{(.*?)\\}"); private static final Pattern namePattern = Pattern.compile("(?<format>[yMd\\-]+):?(?<op>\\+|\\-)?(?<num>[0-9]+)?(?<scale>[a-zA-Z])?"); /** * 正则查找动态日期字符串'${yyyy-MM-dd}或'${yyyyMMdd:+7d}',并进行格式化 * @param sourStr * @return */ public static String dynamicFormat(String sourStr) { String targetStr = sourStr; try { Matcher dynamicMatcher = dynamicPattern.matcher(targetStr); while (dynamicMatcher.find()) { String key = dynamicMatcher.group(); String keyclone = key.substring(2,key.length()-1); String value = getDynamicDate(keyclone); if (value != null) targetStr = targetStr.replace(key, value); } }catch (Exception e){ e.printStackTrace(); } return targetStr; } /** * 根据日期格式返回日期字符串 * @param key 'yyyyMMdd' 或 'yyyy-MM-dd:+1d' * @return 2021-11-23 * 根据格式串分别匹配出日期格式、加or减、加减数量和尺度 */ public static String getDynamicDate(String key) { Matcher dateMatcher = namePattern.matcher(key); LocalDateTime now = LocalDateTime.now(); String target=null; while (dateMatcher.find()) { String format= dateMatcher.group("format"); String op= dateMatcher.group("op"); String num= dateMatcher.group("num"); String scale= dateMatcher.group("scale"); DateFormatEnum dynamicDate = getDateFormat(format); if (op == null) { return dynamicDate.format(now); } DateScaleEnum dateScaleEnum = DateScaleEnum.valueOf(scale); LocalDateTime calculate = dateScaleEnum.calculate(now, op, Integer.valueOf(num)); target= dynamicDate.format(calculate); } return target; } /** * 根据用户提供的format对日期格式化 * @param format * @return */ public static DateFormatEnum getDateFormat(String format) { switch (format) { case "yyyy": return DateFormatEnum.Y; case "yyyy-MM": return DateFormatEnum.YM; case "yyyyMM": return DateFormatEnum.YM3; case "yyyyMMdd": return DateFormatEnum.YMD3; case "yyyy-MM-dd": default: return DateFormatEnum.YMD; } } public static void main(String[] args) { String sql="select a,b,c from table " + "where dt>='${yyyy:-1y}-01' and dt<='${yyyyMM:+10M}' and dt='${yyyy-MM-dd} 00:00:00'"; System.out.println(sql); String newSql = dynamicFormat(sql); System.out.println(newSql); } }
二、宏变量的解释
对宏变量的解析分为两种情况,一种是没有日期加减的情况,只需要根据用户提交的占位符,转换成对应格式的日期
public enum DateFormatEnum { YMD_HMS(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) { public TemporalAccessor parse(String src) { return this.getFormatter().parse(src); } }, YMD(DateTimeFormatter.ofPattern("yyyy-MM-dd")) { public TemporalAccessor parse(String src) { return this.getFormatter().parse(src); } }, YMD2(DateTimeFormatter.ofPattern("yyyy/MM/dd")) { public TemporalAccessor parse(String src) { return this.getFormatter().parse(src); } }, YMD3(DateTimeFormatter.ofPattern("yyyyMMdd")) { public TemporalAccessor parse(String src) { return this.getFormatter().parse(src); } }, YM(DateTimeFormatter.ofPattern("yyyy-MM")) { public TemporalAccessor parse(String src) { DateTimeFormatter formatter = new DateTimeFormatterBuilder() .appendPattern("yyyy-MM") .parseDefaulting(ChronoField.DAY_OF_MONTH, 1) .toFormatter(); return formatter.parse(src); } }, YM2(DateTimeFormatter.ofPattern("yyyy年M月")) { public TemporalAccessor parse(String src) { DateTimeFormatter formatter = new DateTimeFormatterBuilder() .appendPattern("yyyy年M月") .parseDefaulting(ChronoField.DAY_OF_MONTH, 1) .toFormatter(); return formatter.parse(src); } }, YM3(DateTimeFormatter.ofPattern("yyyyMM")) { public TemporalAccessor parse(String src) { DateTimeFormatter formatter = new DateTimeFormatterBuilder() .appendPattern("yyyyMM") .parseDefaulting(ChronoField.DAY_OF_MONTH, 1) .toFormatter(); return formatter.parse(src); } }, Y(DateTimeFormatter.ofPattern("yyyy")) { public TemporalAccessor parse(String src) { DateTimeFormatter formatter = new DateTimeFormatterBuilder() .appendPattern("yyyy") .parseDefaulting(ChronoField.MONTH_OF_YEAR, 1) .parseDefaulting(ChronoField.DAY_OF_MONTH, 1) .toFormatter(); return formatter.parse(src); } }, MD(DateTimeFormatter.ofPattern("MM/dd")) { public TemporalAccessor parse(String src) { int year = LocalDateTime.now().getYear(); DateTimeFormatter formatter = new DateTimeFormatterBuilder() .appendPattern("MM/dd") .parseDefaulting(ChronoField.YEAR, year) .toFormatter(); return formatter.parse(src); } }, MD2(DateTimeFormatter.ofPattern("MM月dd日")) { public TemporalAccessor parse(String src) { int year = LocalDateTime.now().getYear(); DateTimeFormatter formatter = new DateTimeFormatterBuilder() .appendPattern("MM月dd日") .parseDefaulting(ChronoField.YEAR, year) .toFormatter(); return formatter.parse(src); } }, MD_HM(DateTimeFormatter.ofPattern("MM-dd HH:mm")) { public TemporalAccessor parse(String src) { int year = LocalDateTime.now().getYear(); DateTimeFormatter formatter = new DateTimeFormatterBuilder() .appendPattern("MM-dd HH:mm") .parseDefaulting(ChronoField.YEAR, year) .toFormatter(); return formatter.parse(src); } }; private final DateTimeFormatter formatter; DateFormatEnum(DateTimeFormatter formatter) { this.formatter = formatter; } public String format(TemporalAccessor temporalAccessor) { return formatter.format(temporalAccessor); } public DateTimeFormatter getFormatter() { return formatter; } public abstract TemporalAccessor parse(String src); public static void main(String[] args) { String src = "2021/04/01"; TemporalAccessor parse = YMD2.parse(src); String format = YMD.format(parse); System.out.println(format); } }
三、宏变量的计算
在前面的基础上,我们还需要日期的加减,我们只支持加减两种操作。所有没有定义单独的类,而是作为一个参数传进来。
不过加减的度量可以是年月日三种,都在下面的类中定义好了。
public enum DateScaleEnum { y { @Override public LocalDateTime calculate(LocalDateTime origin, String op, Integer num) { if (op.equals("+")) { return origin.plusYears(num); } else { return origin.minusYears(num); } } }, M { @Override public LocalDateTime calculate(LocalDateTime origin, String op, Integer num) { if (op.equals("+")) { return origin.plusMonths(num); } else { return origin.minusMonths(num); } } }, d { @Override public LocalDateTime calculate(LocalDateTime origin, String op, Integer num) { if (op.equals("+")) { return origin.plusDays(num); } else { return origin.minusDays(num); } } }; public abstract LocalDateTime calculate(LocalDateTime origin, String op, Integer num); }
当我们遇到这样一个SQL时:
sql="select a,b,c from table " + "where dt>='${yyyy:-1y}-01' and dt<='${yyyyMM:+10M}' and dt='${yyyy-MM-dd} 00:00:00'";
就可以通过我们的解释规则将'${yyyyMM:+10M}'理解为用户需要10个月之后的年月格式数据,进而替换成用户需要的日期。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· AI Agent开发,如何调用三方的API Function,是通过提示词来发起调用的吗