2

Is it possible to write a TestDataBuilder which follows a given syntax?

So for example:

Carbuilder

I know how to write a basic builder for a car that's not the problem.

But how can I achieve, that I can only add new windows to a door?

So this is allowed:

var car = CarBuilderWithSyntax.Create()
              .WithDoor()
                  .HavingSide(Sides.Left)
                  .WithWindow()
                      .HavingWidth(50)
                      .HavingHeight(50)
            .Build();

But this is not allowed:

var car = CarBuilderWithSyntax.Create()
              .WithWindow()
                  .HavingWidth(50)
                  .HavingHeight(50)
          .Build();

Is there a possibility to enforce this syntax-rule?

Can this be achieved by using an additional door builder which inherits car builder?

Should the car builder implement different interfaces like IDoorBuilderWithSyntax which defines the methods ICarBuilderWithSyntax WithWindow() and ICarBuilderWithSyntax HavingSide(); ICarBuilderWithSyntax HavingColor()?

xeraphim
  • 4,375
  • 9
  • 54
  • 102

1 Answers1

2

You could do something like:

public enum Sides
{
    Left,
}

public class Car
{

}

public class CarBuilderWithSyntax
{
    protected CarBuilderWithSyntax ParentBuilder { get; private set; }

    public static CarBuilderWithSyntax Create()
    {
        return new CarBuilderWithSyntax(null);
    }

    protected CarBuilderWithSyntax(CarBuilderWithSyntax parent)
    {
        ParentBuilder = parent;
    }

    protected CarBuilderWithSyntax GetParentBuilder()
    {
        CarBuilderWithSyntax parentBuilder = this;

        while (parentBuilder.ParentBuilder != null)
        {
            parentBuilder = parentBuilder.ParentBuilder;
        }

        return parentBuilder;
    }

    public DoorBuilder WithDoor()
    {
        return new DoorBuilder(GetParentBuilder());
    }

    public CarBuilderWithSyntax WithEngine(int cmq)
    {
        if (ParentBuilder != null)
        {
            return GetParentBuilder().WithEngine(cmq);
        }

        // Save somewhere this information
        return this;
    }

    public Car Build()
    {
        return null;
    }

    public class DoorBuilder : CarBuilderWithSyntax
    {
        public DoorBuilder(CarBuilderWithSyntax builder)
            : base(builder)
        {
        }

        protected new DoorBuilder GetParentBuilder()
        {
            DoorBuilder parentBuilder = this;

            while ((parentBuilder.ParentBuilder as DoorBuilder) != null)
            {
                parentBuilder = parentBuilder.ParentBuilder as DoorBuilder;
            }

            return parentBuilder;
        }

        public DoorBuilder HavingSide(Sides side)
        {
            // Save side this information somewhere
            return GetParentBuilder();
        }

        public WindowBuilder WithWindow()
        {
            return new WindowBuilder(this);
        }

        public class WindowBuilder : DoorBuilder
        {
            public WindowBuilder(DoorBuilder builder)
                : base(builder)
            {
            }

            public WindowBuilder HavingWidth(int width)
            {
                // Terminal elements don't need to do the GetParentBuilder()
                return this;
            }

            public WindowBuilder HavingHeight(int width)
            {
                // Terminal elements don't need to do the GetParentBuilder()
                return this;
            }
        }
    }
}

Now you only need to choose how/where to save the informations of the Builder... Note how the various classes interconnect, and the various GetParentBuilder() uses.

var car = CarBuilderWithSyntax.Create()
  .WithDoor()
      .HavingSide(Sides.Left)
      .WithWindow()
          .HavingWidth(50)
          .HavingHeight(50)
  .WithEngine(100)
  .Build();
xanatos
  • 109,618
  • 12
  • 197
  • 280