Recasting to Derived Type
I have a problem that I'm not sure how to approach, and I'm hoping the people here will have some good tips.
I am parsing text files, which contain several logs (one log per line). The format is something like the following:
Date Type Description
10/20 A LogTypeADescription
10/20 B LogTypeBDescription
10/20 C LogTypeCDescription
Here, you can see there are three "types" of logs (A, B, and C). Depending on what type the log is, I will parse the "Description" field differently.
My question is how should I set up the data structure? I would like to do something like this:
class Log
{
DateTime Date;
String Type;
String Description;
public Log(String line)
{
Parse(line);
}
}
class ALog : Log { }
class BLog : Log { }
class CLog : Log { }
Now, each derived class can have its own unique properties, depending on how the "Description" field is parsed, and they will still maintain the three "core" properties (Date, Type, and Description).
Everything is good so far, except I don't know what type of (derived) log I need until I have parsed the line from the log file. Of course, I could parse the line, and then figure it out, but I really want the parsing code to be in the "Log" constructor. I wish I could do something like this:
void Parse(String line)
{
String[] pieces = line.Split(' ');
this.Date = DateTime.Parse(pieces[0]);开发者_如何转开发
this.Type = pieces[1];
this.Description = pieces[2];
if(this.Type == "A")
this = new ALog();
else if(this.Type == "B")
this = new BLog();
else if(this.Type == "C")
this = new CLog();
}
But unfortunately I don't think this is possible. I haven't tried yet, but I'm pretty sure doing this:
Log l = new Log(line);
if(l.Type == "A") l = new ALog();
Would either be illegal or destroy all of the parsing I did when I first created the "Log."
Any suggestions?
Remove the constructor and change Parse to a static that returns a Log.
static Log Parse(string line)
{
string[] tokens line.Split(' ');
var log = null;
if (tokens[1] == "A") log = new ALog();
else if (tokens[1] == "B") log = new BLog();
else log = new CLog();
log.Date = tokens[0];
log.Description = tokens[1];
return log;
}
I would factor out the description parsing into an abstract method that could be overridden for the different description types. Only the description varies, so only this part of the line parsing needs factored out into logic contained within derived types.
using System;
class Log
{
DateTime Date;
String Type;
String Description;
public Log(String line)
{
String[] pieces = line.Split(' ');
this.Date = DateTime.Parse(pieces[0]);
this.Type = pieces[1];
LogParser parser = GetParser(this.Type);
this.Description = parser.Parse(pieces[2]);
}
static LogParser GetParser(string type)
{
switch (type)
{
case "A":
return new AParser();
case "B":
return new BParser();
case "C":
return new CParser();
default:
throw new NotSupportedException();
}
}
}
abstract class LogParser { public abstract string Parse(string line);}
class AParser : LogParser { public override string Parse(string line) { /* do parsing for A */ return string.Empty; } }
class BParser : LogParser { public override string Parse(string line) { /* do parsing for B */ return string.Empty; } }
class CParser : LogParser { public override string Parse(string line) { /* do parsing for C */ return string.Empty; } }
You could read the line in a split it like you are, then read the "type" and call the Activator to create one of your concrete types derived from your (probably abstract) base log, passing in your split arguments to the constructor creating a new specific concrete instance.
(Also, the "Type" could be a read-only property in your derived classes since you know value based on the instance type).
Of course, assuming you're not wanting to avoid reflection.
Yet another solution. Put down your OO hammer, and pick up your functional one.
C# has dictionaries and anonymous functions. Have a dictionary of functions that know how to take a Log
and a description and can parse that information and put it into the Log
. Then you just parseDescription[logType](this, description)
.
This means you need a dictionary with 3 functions rather that 3 new classes.
The difference is not so big in this example. But consider if there were 2 different fields in your log entry that might need to be parsed in multiple ways. The class based approach needs 9 new classes while the dictionary approach has 2 dictionaries with 3 functions each. If there were 3 the comparison would be 27 classes versus 3 dictionaries with 3 functions each.
A re-take on joncham's and btilly's approaches:
using System;
class Log
{
DateTime Date;
String Type;
String Description;
Dictionary<string,LogParser> logDictionary;
static Log()
{
logDictionary = new Dictionary<string,LogParser>;
logDictionary.Add("A",new AParser());
logDictionary.Add("B",new BParser());
logDictionary.Add("C",new CParser());
}
public Log(String line)
{
String[] pieces = line.Split(' ');
this.Date = DateTime.Parse(pieces[0]);
this.Type = pieces[1];
LogParser parser = GetParser(this.Type);
this.Description = parser.Parse(pieces[2]);
}
static LogParser GetParser(string type)
{
return logDictionary<string,LogParser>(type);
}
}
abstract class LogParser { public abstract string Parse(string line);}
class AParser : LogParser { public override string Parse(string line) { /* do parsing for A */ return string.Empty; } }
class BParser : LogParser { public override string Parse(string line) { /* do parsing for B */ return string.Empty; } }
class CParser : LogParser { public override string Parse(string line) { /* do parsing for C */ return string.Empty; } }
精彩评论