【问题标题】:Does the Builder Design Pattern violate the Single Responsibility Principle?Builder 设计模式是否违反单一职责原则?
【发布时间】:2012-12-08 22:21:18
【问题描述】:

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

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 负责:

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

【问题讨论】:

    标签: unit-testing design-patterns builder single-responsibility-principle


    【解决方案1】:

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

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

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

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

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

    单元测试呢?

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

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

    【讨论】:

      【解决方案2】:

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

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

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

      【讨论】:

        猜你喜欢
        • 2010-10-22
        • 1970-01-01
        • 1970-01-01
        • 2023-03-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多