Refactoring a sequence of pattern matching
I have the following piece of code (shortened for the example):
while (reader.ready()) {
String line = reader.readLine();
Matcher responseCodeMatcher = responseCodePattern开发者_运维问答.matcher(line);
if (responseCodeMatcher.matches()) {
responseCode = Integer.parseInt(responseCodeMatcher.group(1));
continue;
}
Matcher cacheControlMatcher = cacheControlPattern.matcher(line);
if (cacheControlMatcher.matches()) {
cacheControl = CacheControl.parseString(responseCodeMatcher.group(1));
continue;
}
...
}
The patterns are all static final members of the class. So I have a bunch of patterns and I want to find out for every line if it matches one of them, and if so - do something (which changes from pattern to pattern). Can you think of a way to nicely refactor this somehow? Perhaps a Collection of Patterns that I go over (and then how do I know what to do if it matches?) or some other idea.
Because nobody answered so far, I will, although I don't know Java.
In C# I would create a list of tuples. Item 1 of the tuple is the pattern to check for and item 2 is an anonymous method which contains the pattern specific code to execute. In C#, it would look something like this:
var patterns = new List<Tuple<Pattern, Action<Matcher>>>();
patterns.Add(Tuple.Create(responseCodePattern, matcher =>
{
responseCode = Integer.parseInt(matcher.group(1));
}));
patterns.Add(Tuple.Create(cacheControlPattern, matcher =>
{
cacheControl = CacheControl.parseString(matcher.group(1));
}));
while (reader.ready()) {
String line = reader.readLine();
foreach(var tuple in patterns)
{
Matcher matcher = tuple.Item1.matcher(line);
if(matcher.matches())
{
tuple.Item2(matcher);
break;
}
}
}
I don't know, if this makes any sense to a Java guy, especially with the lambda syntax... Please ask, if not :-)
I ended up refactoring in the following manner. I created a class HttpPatterns
:
package cs236369.proxy.types;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public enum HttpPatterns {
RESPONSE_CODE("^HTTP/1\\.1 (\\d+) .*$"),
CACHE_CONTROL("^Cache-Control: (\\w+)$"),
HOST("^Host: (\\w+)$"),
REQUEST_HEADER("(GET|POST) ([^\\s]+) ([^\\s]+)$"),
ACCEPT_ENCODING("^Accept-Encoding: .*$"),
CONTENT_ENCODING("^Content-Encoding: ([^\\s]+)$");
private final Pattern pattern;
HttpPatterns(String regex) {
pattern = Pattern.compile(regex);
}
public boolean matches(String expression) {
return pattern.matcher(expression).matches();
}
public Object process(String expression) {
Matcher matcher = pattern.matcher(expression);
if (!matcher.matches()) {
throw new RuntimeException("Called `process`, but the expression doesn't match. Call `matches` first.");
}
if (this == RESPONSE_CODE) {
return Integer.parseInt(matcher.group(1));
} else if (this == CACHE_CONTROL) {
return CacheControl.parseString(matcher.group(1));
} else if (this == HOST) {
return matcher.group(1);
} else if (this == REQUEST_HEADER) {
return new RequestHeader(RequestType.parseString(matcher.group(1)), matcher.group(2), matcher.group(3));
} else if (this == CONTENT_ENCODING) {
return ContentEncoding.parseString(matcher.group(1));
} else { //never happens
return null;
}
}
}
And I use it like so:
String line;
while ((line = reader.readLine()) != null) {
if (HttpPatterns.CACHE_CONTROL.matches(line)) {
cacheControl = (CacheControl) HttpPatterns.RESPONSE_CODE.process(line);
} else if (HttpPatterns.REQUEST_HEADER.matches(line)) {
requestHeader = (RequestHeader) HttpPatterns.REQUEST_HEADER.process(line);
} else if (HttpPatterns.HOST.matches(line)) {
requestHost = (String) HttpPatterns.HOST.process(line);
} else if (HttpPatterns.ACCEPT_ENCODING.matches(line)) {
continue;
} else if (line.isEmpty()) {
break;
}
fullRequest += "\r\n" + line;
}
I don't like that I have to cast everything I get, but that's the best solution I found so far.
Ok, here is my short answer: this is not a language question and the answers and comments on here thus far are hilariously off-base. All languages, no matter how reprobate, include types. This is a question about how to detect those types and then act invoke appropriate corresponding actions. The answer is a couple of the patterns from the gang of four book.
First, for the parsing part, I would recommend that you look at that as a Mediator. The actions should know nothing about the patterns or the file, and likewise, the knowledge of the actions should not be infused into the triggering context. You could call this thing a parser, detector, whatever, but the heart of that class is going to be a mapping of the pattern to an appropriate action.
On the action side, the pattern to use, is of course, the Command pattern. There are a lot of possibilities when using command. If you don't need context, then the command is pretty simple, it just has an execute method. If you need to impart some context that is going to change, you can either templatize the commands or create new ones on the fly then invoke them.
精彩评论