这是使用和测试利用工厂模式的类的正确方法吗?

2024-01-21

我对工厂模式没有太多经验,我遇到过一种情况,我认为这是必要的,但我不确定我是否正确实现了该模式,并且我担心它的影响对我的单元测试的可读性有影响。

我创建了一个代码片段,它(根据记忆)近似于我正在工作的场景的本质。如果有人能看一下它并看看我所做的是否合理,我将非常感激。

这是我需要测试的课程:

public class SomeCalculator : ICalculateSomething
{
    private readonly IReducerFactory reducerFactory;
    private IReducer reducer;

    public SomeCalculator(IReducerFactory reducerFactory)
    {
        this.reducerFactory = reducerFactory;
    }

    public SomeCalculator() : this(new ReducerFactory()){}

    public decimal Calculate(SomeObject so)
    {   
        reducer = reducerFactory.Create(so.CalculationMethod);

        decimal calculatedAmount = so.Amount * so.Amount;

        return reducer.Reduce(so, calculatedAmount);
    }
}

以下是一些基本的接口定义...

public interface ICalculateSomething
{
    decimal Calculate(SomeObject so);
}

public interface IReducerFactory
{
    IReducer Create(CalculationMethod cm);
}

public interface IReducer
{
    decimal Reduce(SomeObject so, decimal amount);
}

这是我创建的工厂。我当前的要求让我添加一个特定的Reducer MethodAReducer 以在特定场景中使用,这就是我尝试引入工厂的原因。

public class ReducerFactory : IReducerFactory
{
    public IReducer Create(CalculationMethod cm)
    {
        switch(cm.Method)
        {
            case CalculationMethod.MethodA:
                return new MethodAReducer();
                break;
            default:
                return DefaultMethodReducer();
                break;
        }
    }
}

这些是两种实现的近似值...实现的本质是,它仅在对象处于特定状态时减少数量。

public class MethodAReducer : IReducer
{
    public decimal Reduce(SomeObject so, decimal amount)
    {   
        if(so.isReductionApplicable())
        {
            return so.Amount-5;
        }
        return amount;
    }
}

public class DefaultMethodReducer : IReducer
{
    public decimal Reduce(SomeObject so, decimal amount)
    {
        if(so.isReductionApplicable())
        {
            return so.Amount--;
        }
        return amount;
    }
}

这是我正在使用的测试夹具。我关心的是工厂模式在测试中占用了多少空间,以及它如何降低了测试的可读性。请记住,在我的现实世界类中,我有几个需要模拟的依赖项,这意味着这里的测试比我的现实世界测试所需的要短几行。

[TestFixture]
public class SomeCalculatorTests
{
    private Mock<IReducerFactory> reducerFactory;
    private SomeCalculator someCalculator;

    [Setup]
    public void Setup()
    {
        reducerFactory = new Mock<IReducerFactory>();
        someCalculator = new SomeCalculator(reducerFactory.Object);     
    }

    [Teardown]
    public void Teardown(){}

第一次测试

    //verify that we can calculate an amount
    [Test]
    public void Calculate_CalculateTheAmount_ReturnsTheAmount()
    {
        decimal amount = 10;
        decimal expectedAmount = 100;
        SomeObject so = new SomeObjectBuilder()
         .WithCalculationMethod(new CalculationMethodBuilder())                                                          
                     .WithAmount(amount);

        Mock<IReducer> reducer = new Mock<IReducer>();

        reducer
            .Setup(p => p.Reduce(so, expectedAmount))
            .Returns(expectedAmount);

        reducerFactory
            .Setup(p => p.Create(It.IsAny<CalculationMethod>))
            .Returns(reducer);

        decimal actualAmount = someCalculator.Calculate(so);

        Assert.That(actualAmount, Is.EqualTo(expectedAmount));
    }

第二次测试

    //Verify that we make the call to reduce the calculated amount
    [Test]
    public void Calculate_CalculateTheAmount_ReducesTheAmount()
    {
        decimal amount = 10;
        decimal expectedAmount = 100;
        SomeObject so = new SomeObjectBuilder()
         .WithCalculationMethod(new CalculationMethodBuilder())                                                          
                     .WithAmount(amount);

        Mock<IReducer> reducer = new Mock<IReducer>();

        reducer
            .Setup(p => p.Reduce(so, expectedAmount))
            .Returns(expectedAmount);

        reducerFactory
            .Setup(p => p.Create(It.IsAny<CalculationMethod>))
            .Returns(reducer);

        decimal actualAmount = someCalculator.Calculate(so);

        reducer.Verify(p => p.Reduce(so, expectedAmount), Times.Once());            
    }
}

那么这一切看起来正确吗?或者有更好的方法来使用工厂模式吗?


您问的问题很长,但这里有一些零散的想法:

  • AFAIK,没有“工厂”模式。有一种模式叫做抽象工厂另一个叫工厂方法。现在您似乎正在使用抽象工厂。
  • SomeCalculator 没有理由同时具有reducerFactory and a reducer场地。摆脱其中之一 - 在您当前的实现中,您不需要reducer field.
  • 使注入的依赖项(reducerFactory) 只读。
  • 摆脱默认构造函数。
  • ReducerFactory 中的 switch 语句可能有代码味道。也许您可以将创建方法移至 CalculationMethod 类。这本质上会将抽象工厂更改为工厂方法。

无论如何,引入松散耦合总是会产生开销,但不要认为这样做只是为了可测试性。可测试性实际上只是开放/封闭原则 http://blog.ploeh.dk/2009/06/05/TestabilityIsReallyTheOpenClosedPrinciple.aspx,因此您可以通过多种方式使代码更加灵活,而不仅仅是启用测试。

是的,为此付出的代价很小,但非常值得。


在大多数情况下,注入的依赖项应该是只读的。虽然技术上没有必要,但使用 C# 标记该字段可以提高安全性readonly关键词。

当您决定使用 DI 时,您必须始终如一地使用它。这意味着重载构造函数是另一种反模式。这使得构造函数不明确,也可能导致紧耦合 and 抽象泄漏.

这种级联看起来可能是一个缺点,但实际上是一个优点。当您需要在其他类中创建 SomeCalculator 的新实例时,您必须再次注入它或注入可以创建它的抽象工厂。当您从 SomeCalculator(例如 ISomeCalculator)中提取接口并注入它时,优势就来了。您现在已经有效地将 SomeCalculator 的客户端与 IReducer 和 IReducerFactory 解耦。

您不需要 DI 容器来完成这一切 - 您可以手动连接实例。这就是所谓的Pure DI http://blog.ploeh.dk/2014/06/10/pure-di/.

当谈到将ReducerFactory中的逻辑移至CalculationMethod时,我正在考虑虚拟方法。像这样的东西:

public virtual IReducer CreateReducer()
{
    return new DefaultMethodReducer();
}

对于特殊的 CalculationMethods,您可以重写 CreateReducer 方法并返回不同的化简器:

public override IReducer CreateReducer()
{
    return new MethodAReducer();
}

最后的建议是否有意义取决于我没有的很多信息,所以我只是说你应该consider它 - 在您的具体情况下可能没有意义。

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

这是使用和测试利用工厂模式的类的正确方法吗? 的相关文章

  • 如何使 Windows 窗体的关闭按钮不关闭窗体但使其不可见?

    该表单有一个 NotifyIcon 对象 当用户单击 关闭 按钮时 我希望表单不关闭而是变得不可见 然后 如果用户想再次查看该表单 可以双击系统托盘中的图标 如果用户想关闭表单 可以右键单击该图标并选择 关闭 有人可以告诉我如何使关闭按钮不
  • 是否可以强制 XMLWriter 将元素写入单引号中?

    这是我的代码 var ptFirstName tboxFirstName Text writer WriteAttributeString first ptFirstName 请注意 即使我使用 ptFirstName 也会以双引号结束 p
  • Qt-Qlist 检查包含自定义类

    有没有办法覆盖加载自定义类的 Qt QList 的比较机制 即在 java 中你只需要重写一个比较方法 我有一个带有我的自定义类模型的 QList QList
  • UML类图:抽象方法和属性是这样写的吗?

    当我第一次为一个小型 C 项目创建 uml 类图时 我在属性方面遇到了一些麻烦 最后我只是将属性添加为变量 lt
  • 如何在列表框项目之间画一条线

    我希望能够用水平线分隔列表框中的每个项目 这只是我用于绘制项目的一些代码 private void symptomsList DrawItem object sender System Windows Forms DrawItemEvent
  • C++ 子字符串返回错误结果

    我有这个字符串 std string date 20121020 我正在做 std cout lt lt Date lt lt date lt lt n std cout lt lt Year lt lt date substr 0 4 l
  • 将布尔参数传递给 SQL Server 存储过程

    我早些时候问过这个问题 我以为我找到了问题所在 但我没有 我在将布尔参数传递给存储过程时遇到问题 这是我的 C 代码 public bool upload false protected void showDate object sende
  • WPF TabControl,用C#代码更改TabItem的背景颜色

    嗨 我认为这是一个初学者的问题 我搜索了所有相关问题 但所有这些都由 xaml 回答 但是 我需要的是后台代码 我有一个 TabControl 我需要设置其项目的背景颜色 我需要在选择 取消选择和悬停时为项目设置不同的颜色 非常感谢你的帮助
  • Web API - 访问 DbContext 类中的 HttpContext

    在我的 C Web API 应用程序中 我添加了CreatedDate and CreatedBy所有表中的列 现在 每当在任何表中添加新记录时 我想填充这些列 为此目的我已经覆盖SaveChanges and SaveChangesAsy
  • C# 中的递归自定义配置

    我正在尝试创建一个遵循以下递归结构的自定义配置部分
  • 在数据库中搜索时忽略空文本框

    此代码能够搜索数据并将其加载到DataGridView基于搜索表单文本框中提供的值 如果我将任何文本框留空 则不会有搜索结果 因为 SQL 查询是用 AND 组合的 如何在搜索 从 SQL 查询或 C 代码 时忽略空文本框 private
  • 将自定义元数据添加到 jpeg 文件

    我正在开发一个图像处理项目 C 我需要在处理完成后将自定义元数据写入 jpeg 文件 我怎样才能做到这一点 有没有可用的图书馆可以做到这一点 如果您正在谈论 EXIF 元数据 您可能需要查看exiv2 http www exiv2 org
  • 需要哪个版本的 Visual C++ 运行时库?

    microsoft 的最新 vcredist 2010 版 是否包含以前的版本 2008 SP1 和 2005 SP1 还是我需要安装全部 3 个版本 谢谢 你需要所有这些
  • 如何让Gtk+窗口背景透明?

    我想让 Gtk 窗口的背景透明 以便只有窗口中的小部件可见 我找到了一些教程 http mikehearn wordpress com 2006 03 26 gtk windows with alpha channels https web
  • 将文本叠加在图像背景上并转换为 PDF

    使用 NET 我想以编程方式创建一个 PDF 它仅包含一个背景图像 其上有两个具有不同字体和位置的标签 我已阅读过有关现有 PDF 库的信息 但不知道 如果适用 哪一个对于如此简单的任务来说最简单 有人愿意指导我吗 P D 我不想使用生成的
  • 在 Dynamics CRM 插件中访问电子邮件发件人地址

    我正在编写一个 Dynamics CRM 2011 插件 该插件挂钩到电子邮件实体的更新后事件 阶段 40 pipeline http msdn microsoft com en us library gg327941 aspx 并且在此阶
  • Validation.ErrorTemplate 的 Wpf 动态资源查找

    在我的 App xaml 中 我定义了一个资源Validation ErrorTemplate 这取决于动态BorderBrush资源 我打算定义独特的BorderBrush在我拥有的每个窗口以及窗口内的不同块内
  • 如何在 C++ BOOST 中像图形一样加载 TIFF 图像

    我想要加载一个 tiff 图像 带有带有浮点值的像素的 GEOTIFF 例如 boost C 中的图形 我是 C 的新手 我的目标是使用从源 A 到目标 B 的双向 Dijkstra 来获得更高的性能 Boost GIL load tiif
  • 防止索引超出范围错误

    我想编写对某些条件的检查 而不必使用 try catch 并且我想避免出现 Index Out of Range 错误的可能性 if array Element 0 Object Length gt 0 array Element 1 Ob
  • 恢复上传文件控制

    我确实阅读了以下帖子 C 暂停 恢复上传 https stackoverflow com questions 1048330 pause resume upload in c 使用 HTTP 恢复上传 https stackoverflow

随机推荐

  • 如何从 docker-compose 命令运行 2 个不同的命令:

    我想从 docker compose 为我的服务运行 2 个不同的命令 bash脚本 sh 配置 etc config yaml 目前 我的 docker compose 如下所示 我希望 bash 脚本在配置命令之后运行 docker c
  • 维基百科Python API

    我正在尝试使用 Python 的维基百科 API 查看维基百科页面中的目录 这是我的代码 gt gt gt import wikipedia gt gt gt ny wikipedia page New York gt gt gt ny s
  • Browser.ReadyState 上的致命执行错误[重复]

    这个问题在这里已经有答案了 可能的重复 NET 致命执行引擎错误 故障排除 https stackoverflow com questions 2823440 troubleshooting net fatal execution engi
  • VB.NET 的表达式主体成员?

    VB NET 支持表达式主体成员吗 到目前为止 它似乎拥有 C 中的所有内容 例如 null 条件 nameof 内插字符串 只能通过 ctor 访问的无实体自动属性等 在 C 中 语法为 string FullName gt FirstN
  • 单击鼠标获取鼠标坐标

    我正在使用下面的代码 但它并不像我想要的那样工作 而且我不知道如何实际制作它 我想要它做的是获取鼠标坐标onClick 但这发生在用户确认消息框之后 消息框 gt 用户单击确定 gt 用户单击屏幕上的任意位置 gt 获取坐标 我应该在 确定
  • Axios 请求失败,状态代码 429,但它正在与 Postman 一起使用

    我正在尝试使用访问此 APIaxios但我收到错误 状态 429 请求太多 我只发送一个请求 但仍然出现错误 但是当我尝试使用邮递员访问此网址时 它正在工作 axios post https www expedia com Hotel Se
  • Objective-C - 将图像转换为 icns

    我正在尝试为 Mac OS X 创建一个应用程序 它将图像类型转换为 icns 文件 我想知道如何开始这样做 任何建议都会很好 Thanks Kevin 使用 CGImageSource API 例如 CGImageSourceCreate
  • 无法使用 ISTIO 网关和虚拟服务连接到 HTTPS 服务

    由于我和我的所有团队成员都是 Istio 的新手 如果我们能在这里获得一些帮助 我们将不胜感激 Problem我已按照以下文档使用应用程序证书和密钥在 k8s 中创建证书并创建机密 https istio io docs tasks tra
  • 缩放单例

    在花了几个小时思考基于服务器的应用程序的一些架构问题之后 我觉得我将不得不使用单例来实现我的目标 纯粹出于以下原因 证明我的气味是合理的 我不需要将昂贵的对象传递到调用堆栈深处 我可以在任何上下文中对单例管理对象执行功能 很多代码已经存在
  • Swagger 标头定义

    我似乎找不到是否可以声明标头对象以便在响应标头中重用它 有为响应模式定义对象的示例 但它不会转置为响应标头 我只设法制作了一个可重用的响应对象 如下所示 responses DownloadOk description Dowload Ok
  • OS X clang -pthread

    在 OS X 中使用 pthread 库和 clang 的编译器 链接器要求是什么 对于 GCC 我知道使用 pthread 设置适当的编译器 链接器选项 但我不确定 OS X 与 clang 的情况 air jose clang c te
  • Symfony2 和 Doctrine2:没有为实体“X”指定标识符/主键。每个实体必须有一个标识符/主键

    我正在使用 Symfony2 创建一些虚拟项目 我遵循 Symfony2 Book 文档来使用 Doctrine 命令行创建实体 http symfony com doc current book doctrine html add map
  • 与 apache Web 服务器和 tomcat 服务器的粘性会话

    我使用 apache Web 服务器作为 apache 后面的两个 tomcat 实例的负载平衡器 当第一个请求发送到节点 A 而来自同一客户端的第二个请求发送到节点 B 时 我无法访问节点 A 内的会话变量 这是显而易见的 我在互联网上冲
  • 如何创建适合移动和桌面浏览器的平面图?

    想要创建一个办公室的动态平面图 以显示占用情况并链接到会议等 我手头有一些 AutoCAD 文件 并且一直在研究在浏览器上制作此文件的方法 在我看来 SVG 将是一个很好的竞争者 它支持大多数移动和桌面浏览器 请不要使用旧版本的 IE 但我
  • Rust 似乎在内存中为布尔数组分配与 8 位整数数组相同的空间

    Running fn main println std mem size of lt u8 1024 gt println std mem size of lt bool 1024 gt 1024 1024 这不是我所期望的 所以我编译并在
  • 找不到 Alamofire 框架

    我正在尝试将 alamofire 安装到我的项目中 以便我可以将图像上传到我的服务器 但是我似乎找不到alamofire framework文件 我已经下载了两次 git 完成了安装说明https github com Alamofire
  • SQL NOT IN 子句

    我有一个查询未按预期工作 Q1 SELECT id name FROM vw x WHERE id NOT IN select pid from table x GROUP BY id name Having max c date gt G
  • SQL Server:存储过程的 EXECUTE AS 子句未授予 sysadmin 权限

    我开发了一个存储过程 以便从备份文件恢复数据库并向其中添加应用程序用户 该存储过程属于master 数据库 问题是我的 IT 部门不允许我使用管理员用户 只能使用 sysadmin 用户的 EXECUTE AS 语句 我可以恢复数据库 但找
  • 为什么要设置线程的Terminal属性?

    我有多线程应用程序 procedure TGridUpdater Execute begin inherited CodeSite Send Thread executed sp ConnectionFactory GetConnectio
  • 这是使用和测试利用工厂模式的类的正确方法吗?

    我对工厂模式没有太多经验 我遇到过一种情况 我认为这是必要的 但我不确定我是否正确实现了该模式 并且我担心它的影响对我的单元测试的可读性有影响 我创建了一个代码片段 它 根据记忆 近似于我正在工作的场景的本质 如果有人能看一下它并看看我所做