5

我创建了一个流利的构建器样式模式来帮助加载我的测试数据。某些方法的顺序很重要,并且想知道管理正确顺序的首选方法是什么。

我目前有以下内容:

using NUnit.Framework;

[TestFixture]
public class DataBuilderTests
{
    [Test]
    public void Can_NAME()
    {
        new DataLoader()
            .Start() // must be called first
            .Setup() // then called next
            .LoadEmployees() // optional order not NB
            .LoadProducts() // optional order not NB
            .StartCleanup() // begin cleanup
            .CleanupEmployees() // optional order not NB
            .CleanupProducts() // optional order not NB
            .End();
    }
}

public class DataLoader
{
    public DataBuilderSetup Start()
    {
        return new DataBuilderSetup(this);       
    }
}

public class DataBuilderSetup
{
    private readonly DataLoader _dataLoader;

    public DataBuilderSetup(DataLoader dataLoader)
    {
        _dataLoader = dataLoader;
    }

    public DataBuilderOptions Setup()
    {
        // do setup
        return new DataBuilderOptions(_dataLoader);
    }
}

public class DataBuilderOptions
{
    private readonly DataLoader _dataLoader;

    public DataBuilderOptions(DataLoader dataLoader)
    {
        _dataLoader = dataLoader;
    }

    public DataBuilderOptions LoadEmployees()
    {
        // load
        return this;
    }

    public DataBuilderOptions LoadProducts()
    {
        // load
        return this;
    }

    public DataBuilderCleanupOptions StartCleanup()
    {
        return new DataBuilderCleanupOptions(_dataLoader);
    }
}

public class DataBuilderCleanupOptions
{
    private readonly DataLoader _dataLoader;

    public DataBuilderCleanupOptions(DataLoader dataLoader)
    {
        _dataLoader = dataLoader;
    }

    public DataBuilderCleanupOptions CleanupEmployees()
    {
        // cleanup
        return this;
    }

    public DataBuilderCleanupOptions CleanupProducts()
    {
        // cleanup
        return this;
    }

    public DataLoader End()
    {
        return _dataLoader;
    }
}
4

5 回答 5

4

在 Java 中(C# 和它的多重继承不应该让它有任何不同)你可以这样做:

声明一组接口,只包含一个方法:

Interface DoFirstThing { // could be renamed to "BuilderOnStart" or "BuilderStartingState"
    DoSecondThing doFirst();
}

Interface DoSecondThing {
    DoLastThing doSecond();
}

Interface DoLastThing {
    BuilderReady doLast();
}

Interface BuilderReady {
    Result build();
}

class BuilderWithForcedSequence implements DoFirstThing, DoSecondThing, DoLastThing, BuilderReady {

     // implement all

}

最后,您需要一些工厂或工厂方法来设置该构建器的初始状态:

public DoFirstThing createNewBuilderWithForcedSequence(requiredParameters){
    return new BuilderWithForcedSequence(requiredParameters);
}

这将产生 Builder,强制排序构建方法(它们应该被重命名为doThat有意义的东西),只能调用doFirst(), 之后doSecond()......等等,直到最终状态,当 obiect 将被构建时,使用build()方法。

Result result = builder.doFirst().doSecond().doLast().build();


编辑:
哎呀,这很准确你的方法:)

于 2012-08-31T11:26:47.637 回答
2

BuilderPattern 的部分优势在于它可以保护消费者免受方法调用的强加“魔术”排序。

我建议更改您的设计,以便:

  • 所有必要的参数都是预先提供的,以保护消费者免受对Start和的强制有序调用Setup
  • 修改您的实体的职责,以便可以任意构建它们。

显然这是个人的喜好。如果这种类型构成将由第三方使用的库的一部分,那么我强烈建议消除对魔术方法排序的需要。如果这仅可能在内部使用,那么由您来权衡更改代码与不更改代码相关的成本。

于 2012-08-31T09:40:58.233 回答
1

首选的方法是不惜一切代价避免它。设计您的构建器,使其清楚需要做什么。

ObjectBuilder
.Init()
.Optional1()
.Optional3()
.Optional2()
.ToObject()

如果需要先发生某些事情,请在构造函数或工厂方法中执行此操作。然后总是有一种方法可以完成构建过程,清理和所有。

于 2012-08-31T09:40:25.830 回答
1

例如,您可以将私有队列成员添加到构建器,Queue<string>并在每个构建器步骤中添加操作名称。

.Build()方法或在您的情况下.End()将检查队列是否以正确的顺序包含正确的操作名称。如果没有,你可以抛出一个InvalidOperationException

您也可以使用树作为数据结构。树将允许您剖析在之前的构建器步骤中不可用的选项。

但最好考虑其他答案,因为我的方法实际上是一种 hack,它会产生维护问题。

于 2012-08-31T09:40:37.417 回答
0

您当前的解决方案是我提供流畅语法的方法,尽管我不一定说它完全遵循构建器模式。本质上,您所做的是将构建器与状态机提供的限制链接在一起。我发现您所做的事情与任何其他普遍接受的流利配置(例如流利的休眠或流利的断言)相比几乎没有什么区别。

于 2012-08-31T21:13:27.470 回答