当代码依赖于两个对象的子类型时,是否有设计模式可以处理

2024-05-01

我会尽力尽可能明确,以防有比回答我的问题更好的解决方案。

我正在使用 C# 工作。

我有一个报告模板,可以包含任意数量的打开的“功能”。功能可能是信息表、饼图/条形图、列表等。我将报告生成为文本文件或 PDF(将来可能有其他选项)。

到目前为止我有一个IFeature接口,以及实现它的一些功能类型:ChartFeature, ListFeature, ETC。 我读取了从数据库启用的功能列表,并将每个功能与数据 id 一起传递给一个方法,该方法返回一个填充的IFeature正确的类型。

我也有一个IReportWriter接口TextReportWriter和 PdfReportWriter 实现。该接口有一个方法:AddFeature(IFeature).

问题是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;
}

在 PDF 编写器中,上面的内容将被修改以生成 PDF 表格单元格、文本框等。

这感觉很丑。我更喜欢它,因为AddFeature(ListFeature){...}, AddFeature(ChartFeature)因为至少它会检查编译时间,但实际上它只是解决了问题,所以现在如果我正在调用的 IReportWriter 则在外面if(feature is ...).

将显示代码移至该功能中只会扭转问题,因为它需要知道是否应该写入纯文本或 PDF。

有什么建议,还是我最好只使用我所拥有的而忽略我的感受?

编辑: 填写了一些条件,让人们更好地了解正在发生的事情。不要太担心这些示例中的确切代码,我只是随手写下了它。


您的问题的一般情况称为双重分派 - 您需要根据两个参数的运行时类型分派到一个方法,而不仅仅是一个参数(“this”指针)。

处理此问题的一种标准模式称为访问者模式。它的描述可以追溯到最初的设计模式书籍,因此有很多示例和分析。

基本思想是,您有两个一般事物 - 您有元素(即您正在处理的事物)和访问者,它们对元素进行处理。您需要对它们进行动态分派 - 因此调用的实际方法会根据元素和访问者的具体类型而有所不同。

在 C# 中,有点像您的示例,您可以定义一个 IFeatureVisitor 接口,如下所示:

public interface IFeatureVisitor {
    void Visit(ChartFeature feature);
    void Visit(ListFeature feature);
    // ... etc one per type of feature
}

然后,在 IFeature 界面中添加“Accept”方法。

public interface IFeature {
    public void Accept(IFeatureVisitor visitor);
}

您的功能实现将实现 Accept 方法,如下所示:

public class ChartFeature : IFeature {
    public void Accept(IFeatureVisitor visitor) {
        visitor.Visit(this);
    }
}

然后,您的报告编写者将实现 IVisitor 接口并执行每种类型中应执行的操作。

要使用它,它看起来像这样:

var writer = new HtmlReportWriter();
foreach(IFeature feature in document) {
    feature.Accept(writer);
}
writer.FinishUp();

其工作方式是对 Accept 的第一个虚拟调用解析回该功能的具体类型。对 Visit 方法的调用不是虚拟的 - 调用visitor.Visit(this)调用正确的重载,因为此时它知道正在访问的事物的确切静态类型。不保留任何强制转换和类型安全。

当添加新的访问者类型时,这种模式非常有用。当元素(您的情况下的功能)发生变化时,情况会更加痛苦 - 每次添加新元素时,您都需要更新 IVisitor 接口和所有实现。所以要慎重考虑。

正如我提到的,这本书出版已经快 20 年了,所以你可以在书中找到很多关于 Visitor 模式的分析和改进。很有帮助的是,这为您提供了足够的开始来继续您的分析。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

当代码依赖于两个对象的子类型时,是否有设计模式可以处理 的相关文章

  • 无法将 std::min 传递给函数,std::min 的副本有效

    Passing std min函数无法编译 我复制了 libcpp 声明std min进入我的源文件并且它可以工作 std 版本有什么问题 clang 和 gcc 也会发生同样的情况 在 Godbolt 上测试 https godbolt
  • 我如何知道 C 程序的可执行文件是在前台还是后台运行?

    在我的 C 程序中 我想知道我的可执行文件是否像这样在前台运行 a out 或者像这样 a out 如果你是前台工作 getpgrp tcgetpgrp STDOUT FILENO or STDIN FILENO or STDERR FIL
  • 并行化斐波那契序列生成器

    我正在学习并行化 在一项练习中 我得到了一些我应该提高性能的算法 其中之一是斐波那契数列生成器 array 0 0 array 1 1 for q 2 q lt MAX q array q array q 1 array q 2 我怀疑 这
  • 异常堆栈跟踪不显示抛出异常的位置

    通常 当我抛出异常 捕获它并打印出堆栈跟踪时 我会看到抛出异常的调用 导致该异常的调用 导致该异常的调用that 依此类推回到整个程序的根 现在它只向我显示异常所在的调用caught 而不是它所在的地方thrown 我不明白是什么改变导致了
  • C# 处理标准输入

    我目前正在尝试通过命令行断开与网络文件夹的连接 并使用以下代码 System Diagnostics Process process2 new System Diagnostics Process System Diagnostics Pr
  • 如何以编程方式播放 16 位 pcm 数组 [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我有一个包含 16 位 pcm 值的短 数组 我希望能够在不添加任何标题 也不将任何文件保存到内存的情况下播放它 我知道我可能需要一个提供
  • 全局使用和 .NET Standard 2.0

    我最近意识到我可以使用 C 10 功能文件范围的命名空间在 NET Standard 2 0 项目中也可以通过设置
  • C# 编译器数字文字

    有谁知道 C 编译器数字文字修饰符的完整列表 默认情况下 声明 0 使其成为 Int32 声明 0 0 使其成为 Double 我可以在末尾使用文字修饰符 f 来确保某些内容被视为 Single 例如像这样 var x 0 x is Int
  • 如何在win32中使用GetSaveFileName保存文件?

    我编写此代码是为了获取 fileName 来保存我的文件 include stdafx h include
  • 如何考虑子类型的多态性

    里氏替换原则指出 超类型的不变量必须保留在子类型中 我对这个原理和多态性的交叉特别感兴趣 事实上 特别是子类型多态性 参数多态性和 Haskell 类型类似乎就是这种情况 因此 我知道当函数的参数是逆变且返回类型是协变时 函数是子类型 我们
  • 通过 C# Mailkit / Mimekit 发送电子邮件,但出现服务器证书错误

    Visual Studio 2015 中的 0 代码 1 我正在使用 Mailkit 最新版本 1 18 1 1 从我自己的电子邮件服务器发送电子邮件 2 电子邮件服务器具有不受信任的自签名证书 3 我在代码中添加了以下两行 以忽略服务器证
  • 使用 C# 中的 Google 地图 API 和 SSIS 包获取行驶距离

    更新 找到了谷歌距离矩阵并尝试相应地修改我的代码 我在这里收到无效参数错误 return new GeoLocation dstnc uri ToString catch return new GeoLocation 0 0 https 基
  • 无法为 wsdl 文件创建服务引用

    I have wsdl文件和xsd我本地机器上的文件 我想在项目中添加服务引用 我没有网络服务 我只有wsdl file 我收到以下错误 The document was understood but it could not be pro
  • 如何使用 CSI.exe 脚本参数

    当你运行csi exe 安装了 Visual Studio 2015 update 2 您将得到以下语法 Microsoft R Visual C Interactive Compiler version 1 2 0 51106 Copyr
  • 无法在 C# 中为 EventArgs 分配使用派生类型的事件处理程序

    所以我有一个事件声明如下 public event EventHandler OnChangeDetected 然后我有以下处理程序被分配给该事件 myObject OnChangeDetected OnTableChanged 我的理解是
  • 有没有办法直接在函数参数中格式化字符串而不是使用临时字符串?

    我有一个接受字符串 字符数组 作为参数的函数 void enterString char my string 当使用这个函数时 我经常发现自己想要输入格式化的字符串 我使用 sprintf 来做到这一点 然而 我每次都必须创建一个临时字符串
  • 如何在c linux中收听特定接口上的广播?

    我目前可以通过执行以下操作来收听我编写的简单广播服务器 仅广播 hello int fd socket PF INET SOCK DGRAM 0 struct sockaddr in addr memset addr 0 sizeof ad
  • 如何提高环复杂度?

    对于具有大量决策语句 包括 if while for 语句 的方法 循环复杂度会很高 那么我们该如何改进呢 我正在处理一个大项目 我应该减少 CC gt 10 的方法的 CC 并且有很多方法都存在这个问题 下面我将列出一些例如我遇到的问题的
  • 为什么表达式 a = a + b - ( b = a ) 在 C++ 中给出序列点警告?

    以下是测试代码 int main int a 3 int b 4 a a b b a cout lt lt a lt lt a lt lt lt lt b lt lt b lt lt n return 0 编译此命令会出现以下警告 gt g
  • C++ Boost ASIO 简单的周期性定时器?

    我想要一个非常简单的周期性计时器每 50 毫秒调用我的代码 我可以创建一个始终休眠 50 毫秒的线程 但这很痛苦 我可以开始研究用于制作计时器的 Linux API 但它不可移植 I d like使用升压 我只是不确定这是否可能 boost

随机推荐

  • 在 R 中将多个回归表输出到 Word 文档的多个页面中

    我的目标是创建一个多页 Microsoft Word 文档 在连续页面上包含许多格式化回归表输出 理想情况下 这可以使用 R Markdown 来完成 我很幸运地使用Word在Word中制作了格式良好的回归表sjPlot tab model
  • Nhibernate ICriteria 和在查询中使用 Lambda 表达式

    你好 我是 NHibernate 的新手 我有点困惑 假设我们有一个product桌子 让product表有 2 列价格1 和价格2 然后我可以通过 HQL 查询映射的产品实体 如下所示 string queryString from pr
  • 5 位 mt_rand() 数字有多唯一?

    我只是想知道 如果你画出 5 位数字 mt rand 数字有多独特 在示例中 我尝试使用此函数获取 500 个随机数的列表 其中一些是重复的 http www php net manual en function mt rand php h
  • 每个 start_url 已抓取多少个项目

    我使用 scrapy 抓取 1000 个 url 并将抓取的项目存储在 mongodb 中 我想知道每个网址找到了多少个项目 从 scrapy 统计数据我可以看到 item scraped count 3500但是 我需要分别对每个 sta
  • 从 R 运行 powershell 命令:表达式或语句中出现意外标记

    我尝试了以下命令 在 powershell 窗口中有效 system powershell command Get ChildItem Filter html Where Object LastWriteTime ge 11 12 2021
  • 正则表达式 - 匹配包含 2 个或更多 2 个字母元音序列的单词

    我想知道如何匹配包含 2 个或更多 2 个字母元音序列的单词 使用 javascript 版本的正则表达式 例如 visionproof steamier preequip 我现在正在学习正则表达式 这就是我到目前为止所拥有的 它只匹配包含
  • 通过边框拖放调整 div 大小,无需添加额外的标记

    我有一个绝对定位的侧面板 我需要通过拖动此边框来更改其宽度 我还需要更改边框悬停上的光标 是否可以在不添加另一个 div 进行拖动的情况下做到这一点 这是标记 right panel position absolute border lef
  • C# Windows 应用程序中的文件上传

    在我的 C Windows 应用程序中 我想上传 pdf 文件 但在我的工具箱中找不到 FileUpload 控件 我如何在 C Windows 应用程序中上传 pdf 文件 regards 将 OpenFileDialog 控件放在窗体上
  • 在 ASP.net 中使用 NVP API 时 Paypal SetExpressCheckout 出现问题

    Hi 我正在实现 Facebook 游戏和 Paypal 快速结账支付服务之间的集成 我的网站是在 ASP net 中开发的 我使用 NVP API 进行集成 我的问题是我不断收到 10400 错误 订单总计丢失 我的代码是 Set the
  • 下载链接选项在显示标记中不起作用

    您好 我正在使用显示标签库显示表格 它工作正常 但是当我导出链接时 它遇到了麻烦 所以可以帮助我如何做到这一点 我的代码将是这样的
  • 如何在OpenAPI中引用响应组件?

    我正在为我的 API 编写 OpenAPI 定义 我在用components的响应 但当我尝试引用这些组件时 Swagger Editor 显示错误 responses ref components responses 401 ref co
  • SQL 查询中的外语/重音字符

    我正在使用 Java 和 Spring 的 JdbcTemplate 类在 Java 中构建一个 SQL 查询来查询 Postgres 数据库 但是 我在执行包含外来 重音字符的查询时遇到问题 例如 修剪后的 代码 JdbcTemplate
  • Apollo 无法在更新中访问 queryVariables:突变后

    我正在尝试使用 update 在执行突变后更新查询 问题是商店中的查询应用了多个不同的变量 我想更新查询并使用相同的变量返回它 我在文档中发现 updateQueries 有一个包含 queryVariables 的选项 它们是执行查询时使
  • Django 查询集权限

    我正在构建一个相当复杂的Django在电子邮件扫描服务之上使用的应用程序 这Django应用程序是使用 Python 3 5 编写的 该应用程序主要使用Django Rest Framework处理与浏览器前端的通信 我目前遇到的问题是我尝
  • AppDomain.CurrentDomain.GetAssemblies 失败并出现 ReflectionTypeLoadException

    在单元测试期间 我遇到了以下代码的问题 该代码要求所有加载的程序集 var res AppDomain CurrentDomain GetAssemblies SelectMany x gt x GetTypes ToList 此代码失败并
  • SAX:如何获取元素的内容

    我在理解使用 SAX 解析 XML 结构时遇到了一些困难 假设有以下 XML
  • grunt jasmine-node 测试运行两次

    我设置 grunt 来运行 node js 茉莉花测试 由于某种原因 使用此配置 结果总是显示双倍的测试 这是我的配置 我在用着茉莉花节点 https github com jasmine contrib grunt jasmine nod
  • strstr() 函数类似,忽略大小写

    我有两根弦 可以说 str1 One Two Three and str2 two 我想知道是否有任何函数可以检查第一个字符串中第二个字符串的匹配 并返回指向第一个字符串的指针 例如strstr 但它不会将相同的字母 大写或小写 视为两个不
  • 对堆排序有一个直观的理解吗?

    在学校 我们目前正在学习 Java 排序算法 我的作业是堆排序 我读了书 试图尽可能多地了解 但似乎我无法理解这个概念 我并不是要求您为我编写一个 Java 程序 只要您能尽可能简单地向我解释堆排序的工作原理即可 是的 所以基本上你拿一个堆
  • 当代码依赖于两个对象的子类型时,是否有设计模式可以处理

    我会尽力尽可能明确 以防有比回答我的问题更好的解决方案 我正在使用 C 工作 我有一个报告模板 可以包含任意数量的打开的 功能 功能可能是信息表 饼图 条形图 列表等 我将报告生成为文本文件或 PDF 将来可能有其他选项 到目前为止我有一个