2

私の現在のプロジェクトでは、外部のサプライヤーから受け取った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)。

だから、私が実際に欲しいのは次のとおりです。

ParserFactoryclass:このクラスは、特定のデータ型に対して正しいパーサーを返します。将来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と新しい方法の両方で質問に答えると、はるかに多くの工藤が得られます:-)

ありがとう!

4

2 に答える 2

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;
    }
}
于 2012-09-12T14:33:34.007 に答える
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クラスを直接作成できます。

于 2012-09-13T13:52:18.400 に答える