在我目前的项目中,我们会解析从外部供应商处收到的CSV文件。 但是,由于供应商将来会支持XML文件,如果管理层决定我们应该使用XML格式,我想提供一种简单的方法来更改我们的代码。
为此,我们的'worker'类应该只引用数据类,而不知道源是CSV或XML文件。 但是,我们确实有一些专门为一个源文件(目前是CSV)编写的工具(用于调试和测试)。
可能描述有点不清楚,但我希望以下示例能为您提供足够的信息来帮助我。 已经从类中删除了不相关的功能,已经重命名了类/接口,并且仅仅为了示例而整体解决方案已经非常简单。 目前,我有以下设置。
数据'基础'类(可以是任何真正的): 这些类由(一个)解析器返回。他们真正做的唯一事情是包含数据(可以被视为DTO的类型)。
public class Person
{
public string FirstName { ... };
public string LastName { ... };
public int Age { ... };
public Person()
{
}
}
ICsvObject
界面:
CSV数据对象的接口。这里最重要的是LoadFromCsv
方法,因为它将由CsvParser
类
public interface ICsvObject
{
int CsvLineNumber { get; set; }
void LoadFromCsv(IList<string> columns);
}
CSV数据类:
这些通常从数据类继承并实现ICsvObject
接口
public class CsvPerson : Person
{
public int CsvLineNumber { get; set; }
public void LoadFromCsv(IList<string> columns)
{
if (columns.count != 3)
throw new Exception("...");
this.FirstName = columns[0];
this.LastName = columns[1];
this.Age = Convert.ToInt32(columns[2]);
}
}
IParser
界面:
此接口为其他类提供了一种在不知道源文件类型的情况下引用解析器的方法。
public interface IParser<T> where T : new()
{
IList<T> ReadFile(string path);
IList<T> ReadStream(Stream sSource);
IList<T> ReadString(string source);
}
CsvParser
课程:
此类实现IParser
接口,并提供解析CSV文件的方法。
将来,可能会决定为XML文件提供解析器。
public class CsvParser<CsvObjectType> : IParser<CsvObjectType> where CsvObjectType : new(), ICsvObject
{
public IgnoreBlankLines { get; set; }
public ReadFile(string path)
{
...
}
public ReadStream(string path)
{
...
}
public ReadString(string path)
{
List<CsvObjectType> result = new ...;
For each line in the string
{
// Code to get the columns from the current CSV line
...
CsvObjectType item = new CsvObjectType();
item.CsvLineNumber = currentlinenumber;
item.LoadFromCsv(columns);
result.add(item);
}
return result;
}
}
现在我已经解释了一下情况,让我们来解决问题:
'Worker'类不应该打扰我们正在使用的解析器类型。
他们应该从解析器接收的所有内容都是数据对象列表(例如Person),它们不需要ICsvObject
接口提供的额外信息(本例中为CsvLineNumber
以及其他信息)在真实情况下的事情)。
但是,其他工具应该能够获取额外的信息(调试/测试程序......)。
所以,我真正想要的是以下内容:
ParserFactory
课程:
此类返回特定数据类型的正确解析器。
在将来切换到XML时,必须创建XML解析器并更改工厂类。
调用工厂方法的所有其他类应该接收有效的IParser
类而不是特定的解析器。
public class ParserFactory
{
//Instance property
...
public IParser<Person> CreatePersonParser()
{
return new CsvParser<CsvPerson>();
}
}
这样做,无论我们使用什么类型的解析器,工作者类都将调用工厂方法。
之后可以调用ParseFile
方法来提供“基础”数据类的列表。
返回Csv解析器是可以的(它实现了IParser接口)。
但是不支持泛型类型。
返回CsvParser<Person>
对于工厂有效,但Person
类不实现ICsvObject
接口,并且由于通用约束而不能与CsvParser
一起使用。
返回CsvParser类或IParser会要求调用类知道我们正在使用哪个解析器,因此这不是一个选项。 使用两个泛型类型输入(一个用于CsvObject类型,一个用于返回类型)创建CsvParser类也不起作用,因为其他工具应该能够访问ICsvObject接口提供的额外信息。
另外值得一提。这是一个正在修改的旧项目。它仍然是.NET 2.0。 但是,在回答时,您可以使用更新的技术(如扩展方法或LINQ方法)。以.NET 2.0和更新的方式回答这个问题会让你获得更多的工作: - )
谢谢!
答案 0 :(得分:0)
我认为你让它变得更加复杂。
为什么不
public interface IParser
{
// this one should be enough as file and string can be accessed via Stream
IList<Person> ReadStream(Stream sSource);
IList<Person> ReadFile(string path);
IList<Person> ReadString(string source);
}
然后你有
public class CsvParser : IParser { ... }
public class XmlParser : IParser { ... }
我没有看到对CsvPerson / XmlPerson的任何需求。每个解析器实现只构建普通人,例如:
public class CsvParser : IParser
{
public IList<Person> ReadString(string path)
{
List<Person> result = new ...;
For each line in the string
{
// Code to get the columns from the current CSV line
Person p = new Person();
p.Name = columns[0];
p.Age = columns[1].AsInt();
result.add(item);
}
return result;
}
}
答案 1 :(得分:0)
感谢您对此进行调查。
我设法通过创建工人类使用的代理类来找到解决方案:
public class CsvParserProxy<CsvObjectType, ResultObjectType> : IParser<ResultObjectType> where CsvObjectType : new(), ResultObjectType, ICsvObject where ResultObjectType : new()
{
private object _lock;
private CsvParser<CsvObjectType> _CsvParserInstance;
public CsvParser<CsvObjectType> CsvParserInstance {
get {
if (this._CsvParserInstance == null) {
lock ((this._lock)) {
if (this._CsvParserInstance == null) {
this._CsvParserInstance = new CsvParser<CsvObjectType>();
}
}
}
return _CsvParserInstance;
}
}
public IList<ResultObjectType> ReadFile(string path)
{
return this.Convert(this.CsvParserInstance.ReadFile(path));
}
public IList<ResultObjectType> ReadStream(System.IO.Stream sSource)
{
return this.Convert(this.CsvParserInstance.ReadStream(sSource));
}
public IList<ResultObjectType> ReadString(string source)
{
return this.Convert(this.CsvParserInstance.ReadString(source));
}
private List<ResultObjectType> Convert(IList<CsvObjectType> TempResult)
{
List<ResultObjectType> Result = new List<ResultObjectType>();
foreach (CsvObjectType item in TempResult) {
Result.Add(item);
}
return Result;
}
}
然后,工厂类创建返回基础数据对象的CsvParserProxies。 其他人可以直接创建CsvParser类,如果他们想要来自CsvObjects的额外信息。