2012-09-12 57 views
2

在我当前的项目中,我们解析了从外部供应商处收到的CSV文件。 但是,由于供应商将来会支持XML文件,因此如果管理层决定使用XML格式,我想提供一种简单的方法来更改我们的代码。泛型和超类型

为了做到这一点,我们的'工作者'类应该只引用数据类,而不知道源是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; 
    } 
} 

现在,我已经解释了情况一点,让我们的问题: “工人”类不应该与我们所使用的解析器的类型麻烦。 他们应该从解析器收到的是数据对象列表(例如Person),它们不需要接口提供的额外信息(本例中为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和更新的方式回答这个问题将会让你获得更多的kudo :-)

谢谢!

+0

如果您找到一种方法来总结您的核心问题并将其表示在顶端,您可能会在这个非常长的问题上得到更好的参与。 – lance

+3

整个长城的文字,并没有明确的问题。你在问什么? –

+0

实际问题:如何返回一个返回基本数据类(Person,NOT CsvPerson)的IParser类,同时仍然保持创建返回特定数据类(CsvPerson,NOT Person)的CsvParser实例的其他情况的能力。 – Nullius

回答

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的额外信息。

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; 
    } 
} 
+0

事情是,它不仅是关于人。 Person CSV文件包含三列(第一名是名,第二名是姓,第三名是年龄)。但对于公司的CSV文件,例如,我们只有2列...所以它应该被不同的解析...因此LoadFromCsv方法。 另外:像(我知道:-))长描述中提到的,有时,我们需要获得CsvPerson对象包含的额外信息。 – Nullius