私の現在のプロジェクトでは、外部のサプライヤーから受け取ったCSVファイルを解析します。ただし、サプライヤは将来XMLファイルをサポートする予定なので、管理者がXML形式を使用する必要があると判断した場合に、コードを簡単に変更できる方法を提供したいと思います。
これを実現するには、ソースがCSVまたはXMLファイルであることを知らずに、「worker」クラスがデータクラスのみを参照する必要があります。ただし、1つのソースファイル(現時点ではCSV)用に特別に作成されたツール(デバッグとテストに使用)がいくつかあります。
おそらくその説明は少し不明確ですが、次の例が私を助けるのに十分な情報を提供することを願っています。無関係な機能がクラスから削除され、クラス/インターフェースの名前が変更され、例のためだけにソリューション全体が大幅に簡略化されました。現時点では、次の設定があります。
データベースの「ベース」クラス(実際には何でもかまいません):これらのクラスは、パーサー(の1つ)によって返されます。彼らが実際に行うのは、データを含めることだけです(一種の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
この例や実際の状況では他のもの)は必要ありません。ただし、他のツールには、追加情報(デバッグ/テストプログラム...)を取得する機能が必要です(SHOULD)。
だから、私が実際に欲しいのは次のとおりです。
ParserFactory
class:このクラスは、特定のデータ型に対して正しいパーサーを返します。将来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を返すには、呼び出し元のクラスが使用しているパーサーを認識している必要があるため、これはオプションではありません。2つのジェネリック型入力(1つはCsvObject型用、もう1つは戻り型用)を使用してCsvParserクラスを作成することも機能しません。これは、他のツールがICsvObjectインターフェイスによって提供される追加情報にアクセスできる必要があるためです。
また、言及する価値があります。これは変更中の古いプロジェクトです。それはまだ.NET2.0です。ただし、回答するときは、新しい手法(拡張メソッドやLINQメソッドなど)を使用できます。.NET 2.0と新しい方法の両方で質問に答えると、はるかに多くの工藤が得られます:-)
ありがとう!