偶然间了解到了JSQLParser,Github上的源码介绍里面就说,这个工具是用Visitor Pattern
JSqlParser parses an SQL statement and translate it into a hierarchy of Java classes. The generated hierarchy can be navigated using the Visitor Pattern
访问者设计模式的用意就是将操作和数据结构分离开。
男人女人
看了一下《大话设计模式》上的男人女人的例子,用Java描述了一遍,下面是代码:
/**
* 访问者的总接口
*/
public interface Action {
void visit(Man man);
void visit(Woman woman);
}
这个Action
接口就是访问者接口,可以产生不确定个数的实现,用这个Action
类来进行行为的扩展。
/**
* 数据结构的总接口
*/
public interface Person {
void accept(Action action);
}
Person
接口是数据结构接口,这个例子中只有男人和女人,当然这个数据结构和Action
接口中的visit
方法数量息息相关。
// 男人的实现
public class Man implements Person {
@Override
public void accept(Action action) {
action.visit(this);
}
}
// 女人的实现
public class Woman implements Person {
@Override
public void accept(Action action) {
action.visit(this);
}
}
public class ObjectStructure {
private List<Person> elements = new ArrayList<>();
public void attach(Person person) {
elements.add(person);
}
public void detach(Person person) {
elements.remove(person);
}
public void display(Action visitor) {
for (Person element : elements) {
element.accept(visitor);
}
}
}
ObjectStructure
是用来进行具体操作的一个类,里面的attach
方法目的是添加数据结构对象,detach
方法是为了删除数据结构对象。
public class Main {
public static void main(String[] args) {
ObjectStructure objectStructure = new ObjectStructure();
objectStructure.attach(new Man());
objectStructure.attach(new Woman());
// Action的Success行为
objectStructure.display(new Action() {
@Override
public void visit(Man man) {
System.out.println(man.getClass().getSimpleName() + "成功时,背后多半有一个伟大的女人!");
}
@Override
public void visit(Woman woman) {
System.out.println(woman.getClass().getSimpleName() + "成功时,背后大多有一个不成功的男人!");
}
});
// Action的Fail行为
objectStructure.display(new Action() {
@Override
public void visit(Man man) {
System.out.println(man.getClass().getSimpleName() + "失败时,闷头喝酒谁也不用劝!");
}
@Override
public void visit(Woman woman) {
System.out.println(woman.getClass().getSimpleName() + "失败时,眼泪汪汪谁也劝不了!");
}
});
// Action的Loving行为
objectStructure.display(new Action() {
@Override
public void visit(Man man) {
System.out.println(man.getClass().getSimpleName() + "恋爱时,凡事不懂也要装懂!");
}
@Override
public void visit(Woman woman) {
System.out.println(woman.getClass().getSimpleName() + "恋爱时,遇事懂也装作不懂!");
}
});
}
}
可以看到Main
类中有三个Action
接口的实现,每一种实现就相当于是一种行为,可以继续添加实现,来添加行为,这样就做到了数据结构和行为分离了。
运行结果:
Man成功时,背后多半有一个伟大的女人!
Woman成功时,背后大多有一个不成功的男人!
Man失败时,闷头喝酒谁也不用劝!
Woman失败时,眼泪汪汪谁也劝不了!
Man恋爱时,凡是不懂也要装懂!
Woman恋爱时,遇事懂也装作不懂!
下面是一个例子,添加一个Action
的实现。
// Action的Marriage行为
objectStructure.display(new Action() {
@Override
public void visit(Man man) {
System.out.println(man.getClass().getSimpleName() + "结婚时,感慨道:恋爱游戏终结时,‘有妻徒刑’遥无期!");
}
@Override
public void visit(Woman woman) {
System.out.println(woman.getClass().getSimpleName() + "结婚时,欣慰曰:爱情长跑路漫漫,婚姻保险保平安!");
}
});
运行结果多出部分:
Man结婚时,感慨道:恋爱游戏终结时,‘有妻徒刑’遥无期!
Woman结婚时,欣慰曰:爱情长跑路漫漫,婚姻保险保平安!
如果需要什么新操作,可以继续添加Action
的实现类。
字符串解析
这就是访问者模式的基本使用,接下来模拟一下JSQLParser
使用访问者设计模式的方式,为了例子简单,我们解析一个字符串,用访问者模式区分普通字符和数字。
访问者接口:
// 一个String的访问者,它可以有无数个实现类
public interface StringVisitor {
void visitor(CharacterElement characterString);
void visitor(NumberElement numberString);
}
数据结构接口:
// 一个String解析的数据结构,它的实现类只有两个,实现类的数量和访问者接口中的方法数量必须要对应
public interface StringElement {
void accept(StringVisitor stringVisitor);
}
数据结构接口的两个实现类:
// 字符实现类
@Data
@Accessors(chain = true)
public class CharacterElement implements StringElement {
private char element;
@Override
public void accept(StringVisitor stringVisitor) {
stringVisitor.visit(this);
}
}
// 数字实现类
@Data
@Accessors(chain = true)
public class NumberElement implements StringElement {
private char element;
@Override
public void accept(StringVisitor stringVisitor) {
stringVisitor.visit(this);
}
}
接下来的ObjectStructure
类似:
public class ObjectStructure {
private List<StringElement> elements = new ArrayList<>();
public void attach(StringElement element) {
elements.add(element);
}
public void detach(StringElement element) {
elements.remove(element);
}
public void display(StringVisitor visitor) {
for (StringElement element : elements) {
element.accept(visitor);
}
}
}
然后写一个StringParser
工具
public class StringParser {
public static ObjectStructure parse(String str) {
ObjectStructure objectStructure = new ObjectStructure();
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (48 <= c && c <= 57) {
objectStructure.attach(new NumberElement().setElement(c));
} else {
objectStructure.attach(new CharacterElement().setElement(c));
}
}
return objectStructure;
}
}
写一个客户端使用一下这个解析类:
public class Main {
public static void main(String[] args) {
String str = "afdjl1414241lskhlnbalg1";
ObjectStructure parse = StringParser.parse(str);
// 第一种操作
parse.display(new StringVisitor() {
@Override
public void visit(CharacterElement characterString) {
System.out.println("第一种操作:字符:" + characterString.getElement());
}
@Override
public void visit(NumberElement numberString) {
System.out.println("第一种操作:数字:" + numberString.getElement());
}
});
// 第二种操作
parse.display(new StringVisitor() {
@Override
public void visit(CharacterElement characterString) {
System.out.println("第二种操作:字符:" + characterString.getElement());
}
@Override
public void visit(NumberElement numberString) {
System.out.println("第二种操作:数字:" + numberString.getElement());
}
});
}
}
可以发现,现在扩展起来非常的方便,想写几种操作就写几种操作。与上面的那个男人女人例子唯一的区别就是多了个if...else
缺省适配器
那么现在遇到了一个问题,每次我写访问者的时候都要去实现两个方法,这里是两个方法,但有时候有一堆的方法怎么办,难道我只需要解析后的数字,但是也要实现字符相关的方法吗?显然这里可以用到接口适配器模式,又称为"缺省适配器",例如下面这样:
public abstract class StringVisitorAdapter implements StringVisitor {
// 空实现
@Override
public void visit(CharacterElement characterString) {
}
// 空实现
@Override
public void visit(NumberElement numberString) {
}
}
然后使用的时候就不需要重写不必要的方法了:
// 第三种操作
parse.display(new StringVisitorAdapter() {
@Override
public void visit(NumberElement numberString) {
System.out.println("第三种操作:数字:" + numberString.getElement());
}
});
除了缺省适配器,还有两种适配器,一种是类适配器,一种是对象适配器,不过这两种适配器和接口适配器完全不是一个意思。如果有必要,会专门抽出一篇文章做记录。
应用场景和产生的问题
在男人女人例子中,已经确定了数据只有两种,男人和女人,所以才可以在Action
接口中写出两个方法,如果数据不确定的话,那么就需要继续在Action
接口中添加方法,这样就违反了ocp
,访问者模式的优点和缺点就明显了。
优点是可以很容易的去扩展操作,但是缺点是数据结构的变化变的困难了。
这样一来应用场景就更加清楚了,用在数据结构不变化,但是操作要经常变化的情况中。比如说,解析XML。