What is the preferred way to control order in the builder pattern?

I created a vibrant builder style template that helps with loading data into my tests. The order of certain methods is important and wonders what is the preferred method for managing the correct sequence.

At the moment, I have the following:

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;
    }
}
+5
source share
5 answers

Part of BuilderPattern's strengths is that it can protect consumers from the imposed “magic” ordering of method calls.

I would recommend changing your design to:

  • All the necessary arguments are provided to protect consumers from forced ordered calls before Startand Setup.
  • , .

, . , , . , , , .

+2

, Queue<string> .

.Build() .End() . , InvalidOperationException

. , .

, , .

+1

- . , , .

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

- , factory. , , .

0

Java (# ) :

, :

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

}

, , factory factory, :

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

Builder ( doThat ), doFirst(), doSecond()... , , obiect , build().

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


EDIT:
, :)

0

Your current solution is the approach I would take to provide free syntax, although I would not say that it exactly follows the builder pattern. Essentially, what you do is the chain of builders along with the constraints provided by the state machine. I see very few differences in what you are doing than with any other generally accepted fluent configuration, such as fluent hibernation or smooth statements.

0
source

All Articles