C# 当代码依赖于两个对象的子类型时,是否需要处理设计模式
我会尽量明确,以防有比回答我的问题更好的解决方案 我在C#工作 我有一个报告模板,可以包括任何数量的“功能”打开。功能可能是信息表、饼图/条形图、列表等。我正在以文本文件或PDF格式生成报告(将来可能还有其他选项) 到目前为止,我有一个C# 当代码依赖于两个对象的子类型时,是否需要处理设计模式,c#,oop,design-patterns,C#,Oop,Design Patterns,我会尽量明确,以防有比回答我的问题更好的解决方案 我在C#工作 我有一个报告模板,可以包括任何数量的“功能”打开。功能可能是信息表、饼图/条形图、列表等。我正在以文本文件或PDF格式生成报告(将来可能还有其他选项) 到目前为止,我有一个IFeature界面,以及一些实现它的功能类型:ChartFeature,ListFeature,等等。 我从数据库中读取已启用功能的列表,并将每个功能与数据id一起传递给一个方法,该方法返回一个正确类型的填充的IFeature 我还有一个IReportWrite
IFeature
界面,以及一些实现它的功能类型:ChartFeature
,ListFeature
,等等。
我从数据库中读取已启用功能的列表,并将每个功能与数据id一起传递给一个方法,该方法返回一个正确类型的填充的IFeature
我还有一个IReportWriter
接口,该接口由textreptwriter
和PdfReportWriter实现。该接口有一个方法:AddFeature(IFeature)
问题是每个writer中的AddFeature
最终看起来像:
public void AddFeature(IFeature)
{
InsertSectionBreakIfNeeded();
if(IFeature is TableFeature)
{
TableFeature tf = (TableFeature)feature;
streamWriter.WriteLine(tf.Title);
for(int row=0; row < tf.Data.First.Length; row++)
{
for(int column=0; i < tf.Data.Length; i++)
{
if(i != 0)
{
streamWriter.Write("|");
}
streamWriter.Write(feature.Data[column][row]);
}
}
}
else if(IFeature is ListFeature)
{
ListFeature lf = (ListFeature)feature;
streamWriter.Write(lf.Title + ": ");
bool first = true;
foreach(var v in lf.Data)
{
if(!first)
{
streamWriter.Write(", ");
}
else
{
first = false;
}
streamWriter.Write(v);
}
}
...
else
{
throw new NotImplementedException();
}
sectionBreakNeeded = true;
}
public void AddFeature(IFeature)
{
InsertSectionBreakIfRequired();
如果(IFeature是TableFeature)
{
TableFeature tf=(TableFeature)特性;
streamWriter.WriteLine(tf.Title);
for(int row=0;row
在PDF编写器中,将修改上述内容以生成PDF表格单元格、文本框等
这感觉很难看。我更喜欢它作为AddFeature(ListFeature){…}
,AddFeature(ChartFeature)
,因为至少在编译时检查了它,但实际上它只是将问题转移到了外部,所以现在如果我调用的IReportWriterif(feature is…
将显示代码移动到功能中只会逆转问题,因为它需要知道是编写纯文本还是PDF
有什么建议吗,还是我最好只是利用我所拥有的而忽略我的感受
编辑:
填写一些条件,让人们更好地了解正在发生的事情。不要太担心这些示例中的确切代码,我只是从头开始写的。我想您正在尝试绘制一些东西(例如,将其输出为pdf或文本或其他格式……) 我的猜测是这样的:
interface IReportWriter {
void AddFeature(IFeature feature);
// Some other method to generate the output.
IOutput Render();
// Drawing primitives that every report writer implements
void PrintChar(char c);
void DrawLine(Point begin, Point end);
...
}
// Default implementation for ReportWriters
abstract class AbstractReportWriter {
private IList<IFeature> features = new List<IFeature>();
...
public void AddFeature(IFeature feature) {
this.features.Add(feature);
}
public IOutput Render() {
foreach(var feature in this.features) {
feature.RenderOn(this);
}
}
// Leave the primitives abstract
public abstract void PrintChar(char c);
public abstract void DrawLine(Point begin, Point end);
}
public interface IFeatureVisitor {
void Visit(ChartFeature feature);
void Visit(ListFeature feature);
// ... etc one per type of feature
}
var writer = new HtmlReportWriter();
foreach(IFeature feature in document) {
feature.Accept(writer);
}
writer.FinishUp();
下面是ChartFeature
的一个示例实现:
public class ChartFeature : IFeature {
...
public void RenderOn(IReportWriter report) {
// Draw the chart based on the primitives.
report.DrawLine(..., ...);
...
}
}
我会用一种稍微不同的方式来构建它: 我有一个
IReport
对象,它构成了报告中的所有功能。此对象将具有方法AddFeature(IFeature)
和GenerateReport(IReportWriter)
然后,我将使用IFeature
实现WriteFeature(IReport,IReportWriter)
,这样就可以将功能的实际处理方式委托给功能本身
您构建代码的方式让我认为,没有办法以任何给定编写器都可以处理的格式无关的方式编写特性,所以让对象本身来处理这个问题 问题的一般情况称为双重分派-您需要根据两个参数的运行时类型分派到方法,而不仅仅是一个参数(“this”指针) 处理这个问题的一个标准模式称为访问者模式。它的描述可以追溯到最初的设计模式书,所以有很多例子和分析 基本的想法是你有两个基本的东西-你有元素(你正在处理的东西)和访客,他们在元素上处理。您需要对这两种方法进行动态调度,因此实际调用的方法根据元素和访问者的具体类型而有所不同 在C#中,有点类似于您的示例,您将定义一个IFeatureVisitor接口,如下所示:
interface IReportWriter {
void AddFeature(IFeature feature);
// Some other method to generate the output.
IOutput Render();
// Drawing primitives that every report writer implements
void PrintChar(char c);
void DrawLine(Point begin, Point end);
...
}
// Default implementation for ReportWriters
abstract class AbstractReportWriter {
private IList<IFeature> features = new List<IFeature>();
...
public void AddFeature(IFeature feature) {
this.features.Add(feature);
}
public IOutput Render() {
foreach(var feature in this.features) {
feature.RenderOn(this);
}
}
// Leave the primitives abstract
public abstract void PrintChar(char c);
public abstract void DrawLine(Point begin, Point end);
}
public interface IFeatureVisitor {
void Visit(ChartFeature feature);
void Visit(ListFeature feature);
// ... etc one per type of feature
}
var writer = new HtmlReportWriter();
foreach(IFeature feature in document) {
feature.Accept(writer);
}
writer.FinishUp();
然后,在IFeature界面中添加一个“Accept”方法
您的功能实现将实现如下所示的Accept方法:
public class ChartFeature : IFeature {
public void Accept(IFeatureVisitor visitor) {
visitor.Visit(this);
}
}
然后,您的报告编写者将实现IVisitor接口,并在每种类型中执行它应该执行的任何操作
要使用它,它看起来像这样:
interface IReportWriter {
void AddFeature(IFeature feature);
// Some other method to generate the output.
IOutput Render();
// Drawing primitives that every report writer implements
void PrintChar(char c);
void DrawLine(Point begin, Point end);
...
}
// Default implementation for ReportWriters
abstract class AbstractReportWriter {
private IList<IFeature> features = new List<IFeature>();
...
public void AddFeature(IFeature feature) {
this.features.Add(feature);
}
public IOutput Render() {
foreach(var feature in this.features) {
feature.RenderOn(this);
}
}
// Leave the primitives abstract
public abstract void PrintChar(char c);
public abstract void DrawLine(Point begin, Point end);
}
public interface IFeatureVisitor {
void Visit(ChartFeature feature);
void Visit(ListFeature feature);
// ... etc one per type of feature
}
var writer = new HtmlReportWriter();
foreach(IFeature feature in document) {
feature.Accept(writer);
}
writer.FinishUp();
其工作方式是,接受的第一个虚拟调用解析回特性的具体类型。对Visit方法的调用不是虚拟的-对visitor.Visit的调用(this)
调用正确的重载,因为此时它知道所访问对象的确切静态类型。没有强制类型转换,并保留类型安全性
当添加新的访问者类型时,这种模式非常好。当元素(在您的案例中的特性)发生变化时,会更加痛苦——每次添加新元素时,都需要更新IVisitor接口和所有实现。所以仔细考虑。
正如我提到的,这本书出版已经将近20年了,所以你可以在那里找到很多关于访问者模式的分析和改进。有益的是,这给了你足够的开始来继续你的分析。我会避免访问者,原因有二:1)这很复杂