2012-09-12 59 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