2012-09-12 3 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 데이터 객체 인터페이스. 이 일반적으로 데이터 클래스에서 상속과 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]); 
    } 
} 

을 구현 :이

public interface ICsvObject 
{ 
    int CsvLineNumber { get; set; } 
    void LoadFromCsv(IList<string> columns); 
} 

CSV 데이터 클래스는 CsvParser 클래스에서 사용되기 때문에 여기서 가장 중요한 것은은 LoadFromCsv 방법 IParser interface : 이 인터페이스는 다른 클래스가 소스 파일 유형을 모른 채 파서를 참조 할 수있는 방법을 제공합니다.

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)이며 ICsvObject 인터페이스 (이 예제에서는 CsvLineNumber 및 실제 상황의 다른 것들)가 제공하는 추가 정보는 필요하지 않습니다. 그러나 다른 도구는 추가 정보 (디버그/테스트 프로그램 ...)를 얻을 수 있어야합니다.

그럼, 내가 실제로 원하는 것은 다음

ParserFactory 클래스 : 이 클래스는 특정 데이터 유형에 대한 올바른 파서를 반환합니다. 앞으로 XML로 전환 할 때 XML 파서를 만들고 팩토리 클래스를 변경해야합니다. 팩토리 메서드를 호출하는 다른 모든 클래스는 특정 파서가 아닌 유효한 IParser 클래스를 받아야합니다.

public class ParserFactory 
{ 
    //Instance property 
    ... 

    public IParser<Person> CreatePersonParser() 
    { 
     return new CsvParser<CsvPerson>(); 
    } 
} 

이렇게하면 작업자 클래스는 사용중인 파서 유형에 관계없이 팩토리 메서드를 호출합니다. ParseFile 메소드를 호출하여 '기본'데이터 클래스 목록을 제공 할 수 있습니다. Csv 파서를 반환하면 OK입니다 (IParser 인터페이스 구현). 그러나 제네릭 형식은 지원되지 않습니다. CsvParser<Person>을 반환하는 것은 공장에서 유효하지만 Person 클래스는 ICsvObject 인터페이스를 구현하지 않으며 제네릭 제약으로 인해 CsvParser과 함께 사용할 수 없습니다.

CsvParser 클래스 나 IParser를 반환하면 호출 클래스가 우리가 사용하고있는 파서를 알 수 없으므로 옵션이 아닙니다. 다른 도구가 ICsvObject 인터페이스에서 제공하는 추가 정보에 액세스 할 수 있어야하므로 두 가지 일반 형식 입력 (CsvObject 형식과 반환 형식 중 하나)을 사용하여 CsvParser 클래스를 만들 수도 있습니다.

또한 언급할만한 가치가 있습니다. 이것은 수정중인 오래된 프로젝트입니다. 아직 .NET 2.0입니다. 그러나 응답 할 때 확장 메서드 나 LINQ 메서드와 같은 최신 기술을 사용할 수 있습니다. .NET 2.0 이상에서 질문에 대답하면 훨씬 더 많은 kudo를 얻을 수 있습니다 :-)

고마워!

+0

핵심 질문을 요약하고 맨 위에 표시하는 방법을 찾으면이 매우 긴 질문에 더 적극적으로 참여할 수 있습니다. – lance

+3

텍스트의 전체 위대한 벽과 명확한 질문이 없습니다. 네가 묻는 것은 무엇인가? –

+0

실제 질문 : 특정 데이터 클래스 (CsvPerson, NOT Person)를 반환하는 CsvParser 인스턴스가 생성 된 다른 상황에서도 여전히 기본 데이터 클래스 (Person, NOT CsvPerson)를 반환하는 IParser 클래스를 반환 할 수 있습니까? – 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을 만듭니다. 다른 사용자는 CsvObjects에서 추가 정보를 원하면 직접 CsvParser 클래스를 만들 수 있습니다.

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

것은 사람에 관한 것만이 아닙니다. 사람 CSV 파일에는 세 개의 열이 있습니다 (첫 번째 이름, 두 번째 성 및 세 번째입니다). 그러나 회사 CSV 파일의 경우 예를 들어 2 열 밖에 없을 것입니다 ... 그래서 파싱이 다르게되어야합니다 ...따라서 LoadFromCsv 메소드. 또한 :(나는 알고 있습니다 :-)) 자세한 설명, 때로는 CsvPerson 객체에 포함 된 추가 정보가 필요합니다. – Nullius