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

所以,这是一个非常简单的例子。

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

但是如果我不想将 Product 的字段暴露给某人(或者我不能这样做,因为 Product 是某个库的一部分)。

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

现在会是这个样子吗?

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

所以我的问题不是关于如何以更好的方式解决它,而是关于 Builder 模式本身。

Builder 设计模式是否违反了单一职责原则?

对我来说,通用定义中的 builder 负责:

  1. 收集构造函数参数(或构建器模式的其他实现中的属性值)
  2. 使用收集的属性构造对象
4

2 回答 2

3

我不认为它违反了单一职责原则(SRP)。考虑 wiki 中提到的 SRP 示例:

Martin 将责任定义为改变的理由,并得出结论认为一个类或模块应该有一个,而且只有一个改变的理由。例如,考虑一个编译和打印报告的模块。可以出于两个原因更改这样的模块。首先,报告的内容可以改变。其次,报告的格式可以改变。这两件事因非常不同的原因而改变;一种实质性的,一种化妆品的。

但是对于您提到的情况,如果一个改变另一个也必须改变,即如果有一个额外的参数,那么应该用这个额外的参数构造对象。所以它本质上是 2 个子任务的 1 个责任。

于 2012-12-08T14:21:18.613 回答
3

我们在这里处理两个概念:

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

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

  • 收集属性
  • 根据收集的属性创建产品

实际上,只有一项职责就是根据收集到的属性创建产品。你看,负责收集属性的类是 Director 类(参见Wikipedia 链接)。Builder 仅以被动方式接收属性。这真的不是它的责任。收到属性后,它会构建对象。

那么单元测试呢?

好吧,从技术上讲,当您不想在模式中公开字段时,这是有原因的,它是您核心设计的一部分。因此,容纳单元测试人员并不是“设计”的工作。适应“设计”是单元测试人员的工作。

您可以通过反射来实现这一点(好吧,那是作弊),或者购买创建一个模型构建器来继承您要测试的 Concrete Builder。这个模型构建器将存储它组装的部分,并使单元测试人员可以访问它们。

于 2012-12-07T02:01:04.990 回答