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

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(使用前将#替换为@)

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

  • 如何将 protobuf-net 与不可变值类型一起使用?

    假设我有一个像这样的不可变值类型 Serializable DataContract public struct MyValueType ISerializable private readonly int x private readon
  • MVC 在布局代码之前执行视图代码并破坏我的脚本顺序

    我正在尝试将所有 javascript 包含内容移至页面底部 我正在将 MVC 与 Razor 一起使用 我编写了一个辅助方法来注册脚本 它按注册顺序保留脚本 并排除重复的内容 Html RegisterScript scripts som
  • 错误:表达式不产生值

    我尝试将以下 C 代码转换为 VB NET 但在编译代码时出现 表达式不产生值 错误 C Code return Fluently Configure Mappings m gt m FluentMappings AddFromAssemb
  • 如何创建包含 IPv4 地址的文本框? [复制]

    这个问题在这里已经有答案了 如何制作一个这样的文本框 我想所有的用户都见过这个并且知道它的功能 您可以使用带有 Mask 的 MaskedTestBox000 000 000 000 欲了解更多信息 请参阅文档 http msdn micr
  • 如何获取 vuejs 组件单元测试中定义的“this”变量

    我正在尝试在 npm 脚本中使用 mocha webpack 来测试 vuejs 组件 我在测试中像这样嘲笑 vuex 商店 const vm new Vue template div div
  • 如何区分用户点击链接和页面自动重定向?

    拥有 C WebBrowser control http msdn microsoft com en us library system windows forms webbrowser aspx在我的 WinForms 应用程序中 并意识
  • 将 Word 文档另存为图像

    我正在使用下面的代码将 Word 文档转换为图像文件 但是图片显得太大 内容不适合 有没有办法渲染图片或将图片保存到合适的尺寸 private void btnConvert Click object sender EventArgs e
  • 在 Visual Studio 2010 中从 Fortran 调用 C++ 函数

    我想从 Fortran 调用 C 函数 为此 我在 Visual Studio 2010 中创建了一个 FORTRAN 项目 之后 我将一个 Cpp 项目添加到该 FORTRAN 项目中 当我要构建程序时出现以下错误 Error 1 unr
  • qdbusxml2cpp 未知类型

    在使用 qdbusxml2cpp 程序将以下 xml 转换为 Qt 类时 我收到此错误 qdbusxml2cpp c ObjectManager a ObjectManager ObjectManager cpp xml object ma
  • 从 Linux 内核模块中调用用户空间函数

    我正在编写一个简单的 Linux 字符设备驱动程序 以通过 I O 端口将数据输出到硬件 我有一个执行浮点运算的函数来计算硬件的正确输出 不幸的是 这意味着我需要将此函数保留在用户空间中 因为 Linux 内核不能很好地处理浮点运算 这是设
  • 标准化 UTF-8 到底是什么?

    The 重症监护室项目 http userguide icu project org transforms normalization 现在也有一个PHP库 http us php net manual en class normalize
  • 如何禁用 fread() 中的缓冲?

    我正在使用 fread 和 fwrite 读取和写入套接字 我相信这些函数用于缓冲输入和输出 有什么方法可以在仍然使用这些功能的同时禁用缓冲吗 Edit 我正在构建一个远程桌面应用程序 远程客户端似乎 落后于服务器 我不知道可能是什么原因
  • C# 中的合并运算符?

    我想我记得看到过类似的东西 三元运算符 http msdn microsoft com en us library ty67wk28 28VS 80 29 aspx在 C 中 它只有两部分 如果变量值不为空 则返回变量值 如果为空 则返回默
  • 外键与独立关系 - Entity Framework 5 有改进吗?

    我读过了several http www ladislavmrnka com 2011 05 foreign key vs independent associations in ef 4 文章和问题 https stackoverflow
  • CMake 无法确定目标的链接器语言

    首先 我查看了this https stackoverflow com questions 11801186 cmake unable to determine linker language with c发帖并找不到解决我的问题的方法 我
  • “接口”类似于 boost::bind 的语义

    我希望能够将 Java 的接口语义与 C 结合起来 起初 我用过boost signal为给定事件回调显式注册的成员函数 这非常有效 但后来我发现一些函数回调池是相关的 因此将它们抽象出来并立即注册所有实例的相关回调是有意义的 但我了解到的
  • 如何设置 log4net 每天将我的文件记录到不同的文件夹中?

    我想将每天的所有日志保存在名为 YYYYMMdd 的文件夹中 log4net 应该根据系统日期时间处理创建新文件夹 我如何设置它 我想将一天中的所有日志保存到 n 个 1MB 的文件中 我不想重写旧文件 但想真正拥有一天中的所有日志 我该如
  • 将 MQTTNet 服务器与 MQTT.js 客户端结合使用

    我已经启动了一个 MQTT 服务器 就像this https github com chkr1011 MQTTnet tree master例子 该代码托管在 ASP Net Core 2 0 应用程序中 但我尝试过控制台应用程序 但没有成
  • 我的班级应该订阅自己的公共活动吗?

    我正在使用 C 3 0 遵循标准事件模式我有 public event EventHandler
  • Oracle Data Provider for .NET 不支持 Oracle 19.0.48.0.0

    我们刚刚升级到 Oracle 19c 19 3 0 所有应用程序都停止工作并出现以下错误消息 Oracle Data Provider for NET 不支持 Oracle 19 0 48 0 0 我将 Oracle ManagedData

随机推荐

  • 如何从 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
  • 这是使用和测试利用工厂模式的类的正确方法吗?

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