2

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

为此,我们的“工人”类应该只引用数据类,而不知道源是 CSV 或 XML 文件。但是,我们确实有一些专门为一个源文件(目前是 CSV)编写的工具(用于调试和测试)。

可能该描述有点不清楚,但我希望下面的示例能够为您提供足够的信息来帮助我。不相关的功能已从类中剥离,类/接口已被重命名,并且整体解决方案已被大大简化,只是为了示例。目前,我有以下设置。

数据“基础”类(实际上可以是任何东西):这些类由(一个)解析器返回。他们真正做的唯一一件事就是包含数据(可以被视为一种 DTO)。

public class Person
{
    public string FirstName { ... };
    public string LastName { ... };
    public int Age { ... };

    public Person()
    {
    }
}

ICsvObjectinterface: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);
}

CsvParserclass:该类实现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在这个例子中以及真实情况下的其他东西)。然而,其他工具应该有能力获得额外的信息(调试/测试程序......)。

所以,我真正想要的是以下内容:

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 和更新的方式回答这个问题会让你获得更多的荣誉 :-)

谢谢!

4

2 回答 2

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 回答
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 回答