生成器设计模式是否违反单一职责原则?

3

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

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);
}

这里我至少看到一个简单的解决方案 - 修改ConcreteProductBuilder.GetResult来接受一个工厂:
public ConcreteProduct GetResult(IConcreteProductFactory factory)
{
    return factory.Create(part1, part2, part3);
}

实现IConcreteProductFactory有两种方式:

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);
}

我的问题不是如何更好地解决它,而是关于建造者模式本身。

建造者设计模式是否违反单一职责原则?

对我来说,通常定义下的建造者负责以下两个方面:

  1. 收集构造函数参数(或其他实现中的属性值)
  2. 使用收集到的属性构造对象
2个回答

3

我们在处理两个概念:

建造者模式是否遵守单一职责原则?是的。

你可能认为建造者有两个职责:

  • 收集属性
  • 基于收集到的属性创建产品

实际上,只有一个职责,即基于收集到的属性创建产品。 你看,负责收集属性的类是Director类(参见维基百科链接)。建造者只是以被动方式接收属性。这并不是它的责任。在接收属性后,它构建对象。

那么单元测试呢?

技术上讲,当您不想在模式中公开字段时,这是有原因的,并且它是您核心设计的一部分。因此,“设计”不应该为单元测试人员提供便利,而是单元测试人员的职责来适应“设计”。
您可以通过反射(好吧,那是作弊)或创建一个继承要测试的具体构建器的模拟构建器来实现这一点。这个模拟构建器将存储它组装的部分并使它们对单元测试人员可访问。

3
我认为这并不违反单一职责原则(SRP)。考虑维基百科中所提到的SRP示例:
Martin将职责定义为改变的理由,并得出结论,类或模块应该有一个且仅有一个改变的理由。例如,考虑一个编译和打印报告的模块。这样的模块可以因两个原因而改变。首先,报告的内容可以改变。其次,报告的格式可以改变。这两个变化的原因非常不同,一个是实质性的,另一个是表面的。
但就你提出的情况而言,如果一个参数发生了变化,那么另一个参数也必须随之改变,即如果有额外的参数,则应使用此额外的参数构造对象。因此,这本质上是一个责任,具有两个子任务。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接