设计数据模型以进行平面文件转换...委托还是继承?
我有一个维护应用程序,必须将企业数据(来自各种数据库/表)转换为平面文件,每个文件都采用特定的格式,以供遗留应用程序使用。 我有数据模型,就像
public class StatusCode
{
public String Id { get; set; }
public Char Level { get; set; }
public String Description { get; set; }
}
我将从数据源中选择一些子集或所有这些记录一样。 我需要将每个实体映射到文件的一行,这可能需要调整数据(填充、转换或处理 null)。
public delegate String MapEntity<T>(T entity);
public MapEntity<StatusCode> MapStatusCode = delegate(StatusCode entity)
{
return String.Format("{0},{1},{2}",
entity.Id.PadLeft(3, '0'),
entity.Level == 'S' ? 0 : 1,
entity.Description ?? "-");
}
问题是,如何编写转换类? 我是否提供一个接受映射委托的“DefaultFileCreator”?
public interface IFileCreator
{
Byte[] Create<T>(MapEntity<T> map, IEnumerable<T> entities);
}
public class DefaultFileCreator : IFileCreator
{
public Byte[] Create<T>(MapEntity<T> map, IEnumerable<T> entities)
{
StringBuilder sb = new StringBuilder();
foreach (T entity in entities)
sb.AppendLine(map(entity));
return Encoding.Default.GetBytes(sb.ToString());
}
}
...
fileCreator.Create(MapStatusCode, repository<StatusCode>.FindAll());
...
通过此解决方案,我关心应该在何处以及在什么范围内保留映射委托。 以及我将如何在不知道 T
的情况下调用它们(如果我需要的话)。
或者,我是否更改接口并需要具体类中的映射?
public interface IFileCreator<T>
{
Byte[] Create(IEnumerable<T> entities);
}
public abstract class FileCreator : IFileCreator<T>
{
protected abstract String Map(T entity);
public Byte[] Create(IEnumerable<T> entities)
{
StringBuilder sb = new StringBuilder();
foreach (T entity in entities)
sb.AppendLine(Map(entity));
return Encoding.Default.GetBytes(sb.ToString());
}
}
public class StatusCodeFile : FileCreator<StatusCode>
{
public override String Map(T entity)
{
return String.Format("{0},{1},{2}",
entity.Id.PadLeft(3, '0'),
entity.Level == 'S' ? 0 : 1,
entity.Description ?? "-");
}
}
该解决方案在具体类中爆炸,但它们与映射委托一样薄。 而且我觉得与 IFileCreator
和工厂一起工作更舒服。 (再次强调,仅在必要时)。
我假设某些基类很有用,因为 StringBuilder
循环和 Byte[]
编码很简单。 具体类是否应该在基类中设置委托属性(而不是调用抽象方法)? 我应该保留方法上的类型参数(这将如何影响基类/具体类)?
我愿意寻求任何解决方案。 我的主要目标是易于维护。 我现在有 12 个模型/文件,这可能会增加到 21 个。我可能需要在任何文件中插入任意页眉/页脚行(这就是为什么我喜欢可重写的基类方法 Map)。
I have a maintenance application that has to turn enterprise data (from various databases/tables) into flat files, each in a specific format, for consumption by a legacy application. I've got data models like
public class StatusCode
{
public String Id { get; set; }
public Char Level { get; set; }
public String Description { get; set; }
}
I will select some subset or all of these records from the data source. And I need to map each entity to one line of the file, which may require adjusting the data (padding, transforming, or handling null
).
public delegate String MapEntity<T>(T entity);
public MapEntity<StatusCode> MapStatusCode = delegate(StatusCode entity)
{
return String.Format("{0},{1},{2}",
entity.Id.PadLeft(3, '0'),
entity.Level == 'S' ? 0 : 1,
entity.Description ?? "-");
}
The question is, how do I write the transformation classes? Do I provide a "DefaultFileCreator" that takes a mapping delegate?
public interface IFileCreator
{
Byte[] Create<T>(MapEntity<T> map, IEnumerable<T> entities);
}
public class DefaultFileCreator : IFileCreator
{
public Byte[] Create<T>(MapEntity<T> map, IEnumerable<T> entities)
{
StringBuilder sb = new StringBuilder();
foreach (T entity in entities)
sb.AppendLine(map(entity));
return Encoding.Default.GetBytes(sb.ToString());
}
}
...
fileCreator.Create(MapStatusCode, repository<StatusCode>.FindAll());
...
With this solution, I'm concerned about where and in what scope I should keep the mapping delegates. And how I'm going to call them without knowing T
(if I ever need to).
Or, do I change the interface and require the mapping in the concrete classes?
public interface IFileCreator<T>
{
Byte[] Create(IEnumerable<T> entities);
}
public abstract class FileCreator : IFileCreator<T>
{
protected abstract String Map(T entity);
public Byte[] Create(IEnumerable<T> entities)
{
StringBuilder sb = new StringBuilder();
foreach (T entity in entities)
sb.AppendLine(Map(entity));
return Encoding.Default.GetBytes(sb.ToString());
}
}
public class StatusCodeFile : FileCreator<StatusCode>
{
public override String Map(T entity)
{
return String.Format("{0},{1},{2}",
entity.Id.PadLeft(3, '0'),
entity.Level == 'S' ? 0 : 1,
entity.Description ?? "-");
}
}
This solution explodes in concrete classes, but they're as thin as the mapping delegates. And I feel more comfortable working with IFileCreator<T>
and a factory. (again, only if necessary).
I'm assuming some base class is useful as the StringBuilder
loop and Byte[]
encoding are straightforward. Should the concrete class set a delegate property in the base class (rather than calling an abstract method)? Should I keep the type parameter on the method (and how would that affect the base/concrete classes)?
I'm up for any solution. My main goal is ease of maintenance. I have 12 models/files right now and this may increase up to 21. I may have a requirement to insert arbitrary header/footer lines in any file (which is why I like the overrideable base class method, Map).
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
您是否必须为每个可能的映射实际创建具体的子类? 也许可以使用 XML 文件(或数据库)来描述每种文件的格式/内容。 然后,您将拥有一个类,该类采用“FileType”键并使用 XML 中的格式信息来确定如何为该 FileType 构建文件。
Do you have to actually create concrete subclasses for each possible mapping? Maybe it would be possible to instead use an XML file (or a database) to describe the format/contents of each kind of file. You then have a single class that takes a "FileType" key and uses the formatting information from the XML to determine how to build the file for that FileType.
现在我已经编写了一些转换,我倾向于每类映射方法。 我必须能够针对伪造的模型运行单元测试,以确保文件正确构建(针对已知的良好示例文件进行测试)。
我一直将映射委托设为它们所属的“工作流”类的私有(每个模型/文件一个“工作流”)。 我必须将它们公开才能对它们进行单元测试,或输出中间文件内容(工作流程将完成的文件保存到数据存储中)。
每个类的映射似乎更容易测试和分解。
Now that I've written a few of the transformations, I'm leaning towards the mapping-per-class approach,. I have to be able to run unit tests against faked models to ensure the files are built properly (testing them against known good sample files).
I've been making the mapping-delegates private to the "workflow" class they belong in (one "workflow" per model/file). I'd have to make them public to unit test them, or output the intermediate file content (the workflow saves the completed files to a data store).
The mapping-per-class seems a lot more testable and decomposable.