Unit testing 建筑商的设计模式是否违反了单一责任原则?

Unit testing 建筑商的设计模式是否违反了单一责任原则?,unit-testing,design-patterns,builder,single-responsibility-principle,Unit Testing,Design Patterns,Builder,Single Responsibility Principle,让我向您展示我在每个博客中看到的构建器设计模式实现: 单元测试生成器的常见方法如下: 这是一个非常简单的例子 我认为构建器模式是一件非常好的事情。它使您能够将所有可变数据放在一个地方,而将所有其他类保持不变,这对于可测试性来说是很酷的 但如果我不想向某人公开产品的字段,或者因为产品是某个库的一部分而不能这样做,该怎么办呢 我应该如何对构建器进行单元测试 现在会是这样吗? 在这里,我看到了至少一个简单的解决方案—修改ConcreteProductBuilder.GetResult以获取工厂:

让我向您展示我在每个博客中看到的构建器设计模式实现:

单元测试生成器的常见方法如下:

这是一个非常简单的例子

我认为构建器模式是一件非常好的事情。它使您能够将所有可变数据放在一个地方,而将所有其他类保持不变,这对于可测试性来说是很酷的

但如果我不想向某人公开产品的字段,或者因为产品是某个库的一部分而不能这样做,该怎么办呢

我应该如何对构建器进行单元测试

现在会是这样吗?

在这里,我看到了至少一个简单的解决方案—修改ConcreteProductBuilder.GetResult以获取工厂:

并通过两种方式实现IContractProductFactory:

在这种情况下,测试将像以前一样简单:

因此,我的问题不是如何更好地解决它,而是关于建筑模式本身

建筑商的设计模式是否违反了单一责任原则

在我看来,通用定义中的生成器负责:

在生成器模式的其他实现中收集构造函数参数或属性值 使用收集的属性构造对象
我们在这里讨论两个概念:

建筑商模式是否尊重单一责任原则? 对

您可能认为,建筑商有两项责任:

收集属性 基于收集的属性创建产品 事实上,只有一项职责是根据收集的属性创建产品。 您知道,负责收集属性的类是Director类,请参见。生成器仅以被动方式接收属性。这其实不是它的责任。收到属性后,它将构建对象

那么单元测试呢

从技术上讲,当您不想在模式中暴露字段时,这是有原因的,也是您核心设计的一部分。因此,容纳单元测试仪不是设计的工作。单元测试人员的工作就是适应设计


你可以通过反射来实现这一点,好吧,那是欺骗,或者购买创建一个模型构建器来继承你想要测试的混凝土构建器。该实体模型构建器将存储其组装的零件,并使单元测试人员能够访问这些零件。

我们在这里讨论两个概念:

建筑商模式是否尊重单一责任原则? 对

您可能认为,建筑商有两项责任:

收集属性 基于收集的属性创建产品 事实上,只有一项职责是根据收集的属性创建产品。 您知道,负责收集属性的类是Director类,请参见。生成器仅以被动方式接收属性。这其实不是它的责任。收到属性后,它将构建对象

那么单元测试呢

从技术上讲,当您不想在模式中暴露字段时,这是有原因的,也是您核心设计的一部分。因此,容纳单元测试仪不是设计的工作。单元测试人员的工作就是适应设计


你可以通过反射来实现这一点,好吧,那是欺骗,或者购买创建一个模型构建器来继承你想要测试的混凝土构建器。这个模型构建器将存储它组装的零件,并使单元测试人员能够访问它们。

我认为它不违反单一责任原则SRP。考虑在Swip:Wiki中提到的例子:

Martin将责任定义为更改的理由,并得出结论,一个类或模块应该有一个更改的理由,而且只有一个。举个例子,考虑一个编译和打印报表的模块。有两个原因可以改变这样一个模块。首先,报告的内容可以改变。第二,报告的格式可以改变。这两件事的变化原因截然不同;一个是实质性的,一个是装饰性的


但是对于你提到的情况,如果一个改变了,另一个也必须改变,也就是说,如果有一个额外的参数,那么这个对象应该用这个额外的参数来构造。因此,它本质上是一个包含两个子任务的责任。

我不认为它违反了单一责任原则SRP。考虑在Swip:Wiki中提到的例子:

Martin将责任定义为更改的理由,并得出结论,一个类或模块应该有一个更改的理由,而且只有一个。举个例子,考虑一个编译和打印报表的模块。有两个原因可以改变这样一个模块。首先,报告的内容可以改变。第二,报告的格式可以改变。这两件事的变化原因截然不同;一个是实质性的,一个是装饰性的

但是对于ca 正如你提到的,如果一个改变了,另一个也必须改变,也就是说,如果有一个额外的参数,那么对象应该用这个额外的参数来构造。因此,它本质上是一个职责,包含两个子任务

interface IProductBuilder
{
    void BuildPart1(Part1 value);
    void BuildPart2(Part2 value);
    void BuildPart3(Part3 value);
}

class ConcreteProduct
{
    public readonly Part1 Part1;
    public readonly Part2 Part2;
    public readonly Part3 Part3;

    public ConcreteProduct(Part1 part1, Part2 part2, Part3 part3)
    {
        Part1 = part1;
        Part2 = part2;
        Part3 = part3;
    }
}

class ConcreteProductBuilder : IProductBuilder
{
    Part1 _part1;
    Part2 _part2;
    Part3 _part3;

    public void BuildPart1(Part1 value)
    {
        _part1 = value;
    }

    public void BuildPart2(Part2 value)
    {
        _part2 = value;
    }

    public void BuildPart3(Part3 value)
    {
        _part3 = value;
    }

    public ConcreteProduct GetResult()
    {
        return new ConcreteProduct(part1, part2, part3);
    }
}
[TestMethod]
void TestBuilder()
{
    var target = new ConcreteBuilder();

    var part1 = new Part1();
    var part2 = new Part2();
    var part3 = new Part3();

    target.BuildPart1(part1);
    target.BuildPart2(part2);
    target.BuildPart3(part3);

    ConcreteProduct product = target.GetResult();

    Assert.IsNotNull(product);
    Assert.AreEqual(product.Part1, part1);
    Assert.AreEqual(product.Part2, part2);
    Assert.AreEqual(product.Part3, part3);
}
[TestMethod]
void TestBuilder()
{
    var target = new ConcreteProductBuilder();

    var part1 = new Part1();
    var part2 = new Part2();
    var part3 = new Part3();

    target.BuildPart1(part1);
    target.BuildPart2(part2);
    target.BuildPart3(part3);

    ConcreteProduct product = target.GetResult();

    TestConcreteProductBehaviorInUseCase1(product);
    TestConcreteProductBehaviorInUseCase2(product);
    ...
    TestConcreteProductBehaviorInUseCaseN(product);
}
public ConcreteProduct GetResult(IConcreteProductFactory factory)
{
    return factory.Create(part1, part2, part3);
}
public MockConcreteProductFactory
{
    public Part1 Part1;
    public Part2 Part2;
    public Part3 Part3;
    public ConcreteProduct Product;
    public int Calls;

    public ConcreteProduct Create(Part1 part1, Part2 part2, Part3 part3)
    {
        Calls++;

        Part1 = part1;
        Part2 = part2;
        Part3 = part3;

        Product = new ConcreteProduct(part1, part2, part3);
        return Product;
    }
}

public ConcreteProductFactory
{
    public ConcreteProduct Create(Part1 part1, Part2 part2, Part3 part3)
    {
        return new ConcreteProduct(part1, part2, part3);
    }
}
[TestMethod]
void TestBuilder()
{
    var target = new ConcreteBuilder();

    var part1 = new Part1();
    var part2 = new Part2();
    var part3 = new Part3();

    target.BuildPart1(part1);
    target.BuildPart2(part2);
    target.BuildPart3(part3);

    var factory = new MockConcreteProductFactory();

    ConcreteProduct product = target.GetResult(factory);

    Assert.AreEqual(1, factory.Calls);
    Assert.AreSame(factory.Product, product);
    Assert.AreEqual(factory.Part1, part1);
    Assert.AreEqual(factory.Part2, part2);
    Assert.AreEqual(factory.Part3, part3);
}