【发布时间】:2012-10-13 23:39:21
【问题描述】:
问题描述
我想重构一个解析器,使其具有灵活的类似 csv 格式的格式,该格式描述了第一行中的列。根据这些信息,我希望解析器构建具有简单属性但也具有复杂属性的对象,例如 List<String>(空格分隔),例如 Things:
示例数据类型
import java.util.List;
public class Thing {
protected int foo;
protected String bar
protected List<String> baz;
public Thing(int foo, String bar, List<String> baz) {
this.foo = foo;
this.bar = bar;
this.baz = baz;
}
public String toString() {
return "foo: " + foo + ", bar: " + bar + ", baz: " + baz;
}
}
解析器的输入将是文本文件,第一行有列行(逗号分隔),下一行有n 中的数据(逗号分隔)。为了简化测试,我将使用Iterator<String> 作为输入行。这个简单的测试应该说明我想要构建什么:
JUnit 测试
// prepare example string iterator
List<String> lines = new ArrayList<String>();
lines.add("bar,baz,foo");
lines.add("yay,quux quuux,17");
lines.add("hey,qaax qaaax,42");
// test parsed things
List<Thing> things = ThingBuilder.buildThings(lines.iterator());
assertNotNull(things);
assertEquals(2, things.size());
assertEquals("foo: 17, bar: yay, baz: [quux, quuux]", things.get(0).toString());
assertEquals("foo: 42, bar: hey, baz: [qaax, qaaax]", things.get(1).toString());
最简单的方法
- 读取第一行并将其拆分为列名
- 阅读所有其他行并对其执行以下操作:
- 用标记分割行
- 循环遍历它们:
- 对于令牌
i做一个大的switch/else if列名i到 - 转换令牌
i - 将提取的值存储在某处
- 对于令牌
- 收集一切并建立一个
Thing
- 完成。
我对这种方法的问题是内部开关。处理完第一行,应该就清楚如何解析行了。
我想要什么
在带有闭包的语言中,我会尝试以下方法:
- 读取第一行并将其拆分为列名
- 为每个列名创建一个闭包,为给定标记设置正确的值并将其添加到 解析器闭包数组
- 阅读所有其他行并对其执行以下操作:
- 用标记分割行
- 循环遍历它们:
- 使用令牌
i调用解析器闭包i
- 使用令牌
- 收集一切并建立一个
Thing
- 完成。
我尝试了什么
我为所有三个令牌解析器提供了一个简单的接口。他们应该获得一个令牌并将生成的值注入给定的ThingBuilder 的缓存中:
public interface TokenParser {
public void parse(String token, ThingBuilder builder);
}
public class FooParser implements TokenParser {
@Override public void parse(String token, ThingBuilder builder) {
builder.setFoo(Integer.parseInt(token));
}
}
public class BarParser implements TokenParser {
@Override public void parse(String token, ThingBuilder builder) {
builder.setBar(token);
}
}
import java.util.ArrayList;
import java.util.List;
public class BazParser implements TokenParser {
@Override public void parse(String token, ThingBuilder builder) {
List<String> baz = new ArrayList<String>();
for (String s : token.split(" ")) baz.add(s);
builder.setBaz(baz);
}
}
我的ThingBuilder 的buildThings 方法是静态的,并在内部创建一个ThingBuilder 对象,构造函数获取第一(列)行。这也是填充令牌解析器列表的地方。在此之后隐藏的ThingBuilder 对象已准备就绪,并使用以下输入行重复调用buildThing 方法以创建Things 列表:
import java.util.ArrayList;
import java.util.List;
import java.util.Iterator;
public class ThingBuilder {
// single column parsers
protected List<TokenParser> columnParsers;
// thing attribute cache
protected int fooCache;
protected String barCache;
protected List<String> bazCache;
// thing attribute cache setter
public void setFoo(int foo) { fooCache = foo; }
public void setBar(String bar) { barCache = bar; }
public void setBaz(List<String> baz) { bazCache = baz; }
// cleanup helper method
protected void cleanup() {
setFoo(0); setBar(null); setBaz(null);
}
// statically build a list of things from given lines
public static List<Thing> buildThings(Iterator<String> lines) {
// prepare builder with the first line
ThingBuilder builder = new ThingBuilder(lines.next());
// parse things
List<Thing> things = new ArrayList<Thing>();
while (lines.hasNext()) {
things.add(builder.buildThing(lines.next()));
}
return things;
}
// prepares a builder to parse thing lines
protected ThingBuilder(String columnLine) {
// split line into columns
String[] columns = columnLine.split(",");
// prepare a parser for each column
columnParsers = new ArrayList<TokenParser>();
for (String column : columns) {
TokenParser parser;
if (column.equals("foo")) parser = new FooParser();
else if (column.equals("bar")) parser = new BarParser();
else if (column.equals("baz")) parser = new BazParser();
else throw new RuntimeException("unknown column: " + column);
columnParsers.add(parser);
}
}
// builds a thing from a string
protected Thing buildThing(String line) {
// split the line in tokens
String[] tokens = line.split(",");
// let the parsers do the work
for (int i = 0; i < tokens.length; i++) {
columnParsers.get(i).parse(tokens[i], this);
}
// hopefully they're done
Thing thing = new Thing(fooCache, barCache, bazCache);
cleanup();
return thing;
}
}
这可行,但是:
我不喜欢我的解决方案的地方
- 感觉很复杂!
-
公共缓存设置器。应该只允许
TokenParsers 填充构建器缓存。 -
如果我有多个包含
int的列怎么办?我必须为每一列构建一个解析器类还是可以多次使用 IntegerParser 类?这里的问题是,解析器必须调用正确的缓存设置方法。
提前感谢您的提示!
【问题讨论】:
-
顺便说一句,很抱歉使用
else if而不是switch,但我运行的是1.6 :) -
TL;DR - 请使用小型 SSCCE 将您的问题简化为具体问题,一次只问一个问题。
-
我已经尽力了 - 请不要关闭这个问题,前两个答案对于该项目来说价值不菲(使用足够灵活的库来处理所有这些东西)和我的 Java 技能(另一个答案,向我展示了解决问题的代码)!